ruby_spriter 0.6.6 → 0.6.7.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.
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RubySpriter::CompressionManager do
6
+ let(:input_file) { 'E:/test/input.png' }
7
+ let(:output_file) { 'E:/test/output.png' }
8
+ let(:temp_file) { 'E:/test/temp.png' }
9
+
10
+ before do
11
+ allow(File).to receive(:exist?).and_return(true)
12
+ allow(File).to receive(:readable?).and_return(true)
13
+ allow(File).to receive(:size).with(input_file).and_return(100000)
14
+ allow(File).to receive(:size).with(output_file).and_return(80000)
15
+ allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
16
+ allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
17
+ end
18
+
19
+ describe '.compress' do
20
+ it 'compresses PNG file using ImageMagick' do
21
+ magick_cmd = RubySpriter::Platform.imagemagick_convert_cmd
22
+
23
+ expect(Open3).to receive(:capture3) do |cmd|
24
+ expect(cmd).to include(magick_cmd.split.first) # Check for 'convert' or 'magick'
25
+ expect(cmd).to include(input_file)
26
+ expect(cmd).to include(output_file)
27
+ expect(cmd).to include('-strip')
28
+ expect(cmd).to include('-define png:compression-level=9')
29
+ expect(cmd).to include('-define png:compression-filter=5')
30
+ expect(cmd).to include('-define png:compression-strategy=1')
31
+ ['', '', double(success?: true)]
32
+ end
33
+
34
+ described_class.compress(input_file, output_file)
35
+ end
36
+
37
+ it 'raises error if compression fails' do
38
+ allow(Open3).to receive(:capture3).and_return(['', 'error', double(success?: false)])
39
+
40
+ expect {
41
+ described_class.compress(input_file, output_file)
42
+ }.to raise_error(RubySpriter::ProcessingError, /Failed to compress/)
43
+ end
44
+
45
+ it 'validates input file exists and is readable' do
46
+ expect(RubySpriter::Utils::FileHelper).to receive(:validate_readable!).with(input_file)
47
+
48
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
49
+
50
+ described_class.compress(input_file, output_file)
51
+ end
52
+
53
+ it 'validates output file was created' do
54
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
55
+ expect(RubySpriter::Utils::FileHelper).to receive(:validate_exists!).with(output_file)
56
+
57
+ described_class.compress(input_file, output_file)
58
+ end
59
+ end
60
+
61
+ describe '.compress_with_metadata' do
62
+ let(:metadata) { { columns: 4, rows: 4, frames: 16 } }
63
+
64
+ before do
65
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata)
66
+ allow(RubySpriter::MetadataManager).to receive(:embed)
67
+ allow(FileUtils).to receive(:mv)
68
+ allow(File).to receive(:exist?).with(temp_file).and_return(true)
69
+ allow(FileUtils).to receive(:rm_f)
70
+ end
71
+
72
+ it 'reads metadata before compression' do
73
+ expect(RubySpriter::MetadataManager).to receive(:read).with(input_file)
74
+
75
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
76
+
77
+ described_class.compress_with_metadata(input_file, output_file)
78
+ end
79
+
80
+ it 'compresses file with metadata preservation' do
81
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
82
+
83
+ expect(described_class).to receive(:compress).with(input_file, anything, debug: false)
84
+ expect(RubySpriter::MetadataManager).to receive(:embed)
85
+
86
+ described_class.compress_with_metadata(input_file, output_file)
87
+ end
88
+
89
+ it 're-embeds metadata after compression' do
90
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
91
+
92
+ expect(RubySpriter::MetadataManager).to receive(:embed) do |temp, output, **opts|
93
+ expect(opts[:columns]).to eq(4)
94
+ expect(opts[:rows]).to eq(4)
95
+ expect(opts[:frames]).to eq(16)
96
+ end
97
+
98
+ described_class.compress_with_metadata(input_file, output_file)
99
+ end
100
+
101
+ it 'handles files without metadata' do
102
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(nil)
103
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
104
+
105
+ # Should just compress without re-embedding metadata
106
+ expect(RubySpriter::MetadataManager).not_to receive(:embed)
107
+
108
+ described_class.compress_with_metadata(input_file, output_file)
109
+ end
110
+
111
+ it 'cleans up temp file after successful compression' do
112
+ allow(Open3).to receive(:capture3).and_return(['', '', double(success?: true)])
113
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata)
114
+ allow(RubySpriter::MetadataManager).to receive(:embed)
115
+
116
+ # Simulate temp file creation
117
+ temp_path = nil
118
+ allow(described_class).to receive(:compress) do |input, output|
119
+ temp_path = output
120
+ end
121
+
122
+ expect(FileUtils).to receive(:rm_f).with(anything)
123
+
124
+ described_class.compress_with_metadata(input_file, output_file)
125
+ end
126
+ end
127
+
128
+ describe '.compression_stats' do
129
+ it 'returns compression statistics' do
130
+ original_size = 100000
131
+ compressed_size = 80000
132
+
133
+ allow(File).to receive(:size).with(input_file).and_return(original_size)
134
+ allow(File).to receive(:size).with(output_file).and_return(compressed_size)
135
+
136
+ stats = described_class.compression_stats(input_file, output_file)
137
+
138
+ expect(stats[:original_size]).to eq(original_size)
139
+ expect(stats[:compressed_size]).to eq(compressed_size)
140
+ expect(stats[:saved_bytes]).to eq(20000)
141
+ expect(stats[:reduction_percent]).to be_within(0.1).of(20.0)
142
+ end
143
+
144
+ it 'handles case where compressed file is larger' do
145
+ original_size = 100000
146
+ compressed_size = 120000
147
+
148
+ allow(File).to receive(:size).with(input_file).and_return(original_size)
149
+ allow(File).to receive(:size).with(output_file).and_return(compressed_size)
150
+
151
+ stats = described_class.compression_stats(input_file, output_file)
152
+
153
+ expect(stats[:saved_bytes]).to eq(-20000)
154
+ expect(stats[:reduction_percent]).to be_within(0.1).of(-20.0)
155
+ end
156
+ end
157
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require 'securerandom'
4
5
 
5
6
  RSpec.describe RubySpriter::Consolidator do
6
7
  let(:spritesheet1) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x2.png') }
@@ -372,4 +373,166 @@ RSpec.describe RubySpriter::Consolidator do
372
373
  end
373
374
  end
374
375
  end
376
+
377
+ describe '#find_spritesheets_in_directory' do
378
+ let(:consolidator) { described_class.new }
379
+ let(:image_without_meta) { File.join(__dir__, '..', 'fixtures', 'image_without_metadata.png') }
380
+
381
+ # Helper to create a unique test directory for each test
382
+ def create_test_dir
383
+ dir = File.join($test_temp_dir, "consolidate_test_#{SecureRandom.hex(8)}")
384
+ FileUtils.mkdir_p(dir)
385
+ dir
386
+ end
387
+
388
+ it 'finds all PNG files with metadata in directory' do
389
+ test_dir = create_test_dir
390
+ # Copy spritesheets with metadata to test directory
391
+ sprite1_path = File.join(test_dir, 'sprite1.png')
392
+ sprite2_path = File.join(test_dir, 'sprite2.png')
393
+ FileUtils.cp(spritesheet1, sprite1_path)
394
+ FileUtils.cp(spritesheet2, sprite2_path)
395
+
396
+ # Mock metadata reading to return valid metadata for these files
397
+ allow(RubySpriter::MetadataManager).to receive(:read).and_call_original
398
+ allow(RubySpriter::MetadataManager).to receive(:read).with(sprite1_path).and_return(
399
+ { columns: 2, rows: 2, frames: 4, version: '0.6' }
400
+ )
401
+ allow(RubySpriter::MetadataManager).to receive(:read).with(sprite2_path).and_return(
402
+ { columns: 2, rows: 3, frames: 6, version: '0.6' }
403
+ )
404
+
405
+ found_files = consolidator.find_spritesheets_in_directory(test_dir)
406
+
407
+ expect(found_files.length).to eq(2)
408
+ expect(found_files).to include(sprite1_path)
409
+ expect(found_files).to include(sprite2_path)
410
+ end
411
+
412
+ it 'excludes PNG files without metadata' do
413
+ test_dir = create_test_dir
414
+ # Copy two spritesheets with metadata and one image without
415
+ sprite1_path = File.join(test_dir, 'sprite1.png')
416
+ sprite2_path = File.join(test_dir, 'sprite2.png')
417
+ no_meta_path = File.join(test_dir, 'no_meta.png')
418
+ FileUtils.cp(spritesheet1, sprite1_path)
419
+ FileUtils.cp(spritesheet2, sprite2_path)
420
+ FileUtils.cp(image_without_meta, no_meta_path)
421
+
422
+ # Mock metadata reading
423
+ allow(RubySpriter::MetadataManager).to receive(:read).and_call_original
424
+ allow(RubySpriter::MetadataManager).to receive(:read).with(sprite1_path).and_return(
425
+ { columns: 2, rows: 2, frames: 4, version: '0.6' }
426
+ )
427
+ allow(RubySpriter::MetadataManager).to receive(:read).with(sprite2_path).and_return(
428
+ { columns: 2, rows: 3, frames: 6, version: '0.6' }
429
+ )
430
+ allow(RubySpriter::MetadataManager).to receive(:read).with(no_meta_path).and_return(nil)
431
+
432
+ found_files = consolidator.find_spritesheets_in_directory(test_dir)
433
+
434
+ expect(found_files.length).to eq(2)
435
+ expect(found_files).to include(sprite1_path)
436
+ expect(found_files).to include(sprite2_path)
437
+ expect(found_files).not_to include(no_meta_path)
438
+ end
439
+
440
+ it 'returns files sorted alphabetically' do
441
+ test_dir = create_test_dir
442
+ # Copy spritesheets with names that test alphabetical sorting
443
+ c_sprite_path = File.join(test_dir, 'c_sprite.png')
444
+ a_sprite_path = File.join(test_dir, 'a_sprite.png')
445
+ b_sprite_path = File.join(test_dir, 'b_sprite.png')
446
+ FileUtils.cp(spritesheet1, c_sprite_path)
447
+ FileUtils.cp(spritesheet2, a_sprite_path)
448
+ FileUtils.cp(spritesheet3, b_sprite_path)
449
+
450
+ # Mock metadata reading
451
+ allow(RubySpriter::MetadataManager).to receive(:read).and_call_original
452
+ allow(RubySpriter::MetadataManager).to receive(:read).with(c_sprite_path).and_return(
453
+ { columns: 4, rows: 1, frames: 4, version: '0.6' }
454
+ )
455
+ allow(RubySpriter::MetadataManager).to receive(:read).with(a_sprite_path).and_return(
456
+ { columns: 2, rows: 3, frames: 6, version: '0.6' }
457
+ )
458
+ allow(RubySpriter::MetadataManager).to receive(:read).with(b_sprite_path).and_return(
459
+ { columns: 4, rows: 4, frames: 16, version: '0.6' }
460
+ )
461
+
462
+ found_files = consolidator.find_spritesheets_in_directory(test_dir)
463
+
464
+ expect(found_files.length).to eq(3)
465
+ expect(found_files[0]).to end_with('a_sprite.png')
466
+ expect(found_files[1]).to end_with('b_sprite.png')
467
+ expect(found_files[2]).to end_with('c_sprite.png')
468
+ end
469
+
470
+ it 'raises error when directory does not exist' do
471
+ expect {
472
+ consolidator.find_spritesheets_in_directory('nonexistent_directory')
473
+ }.to raise_error(RubySpriter::ValidationError, /Directory not found/)
474
+ end
475
+
476
+ it 'raises error when no PNG files with metadata found' do
477
+ # Create empty directory
478
+ empty_dir = File.join($test_temp_dir, 'empty_dir')
479
+ FileUtils.mkdir_p(empty_dir)
480
+
481
+ expect {
482
+ consolidator.find_spritesheets_in_directory(empty_dir)
483
+ }.to raise_error(RubySpriter::ValidationError, /No PNG files with spritesheet metadata found/)
484
+ end
485
+
486
+ it 'raises error when directory has PNGs but none with metadata' do
487
+ test_dir = create_test_dir
488
+ # Copy only image without metadata
489
+ FileUtils.cp(image_without_meta, File.join(test_dir, 'no_meta.png'))
490
+
491
+ expect {
492
+ consolidator.find_spritesheets_in_directory(test_dir)
493
+ }.to raise_error(RubySpriter::ValidationError, /No PNG files with spritesheet metadata found/)
494
+ end
495
+
496
+ it 'handles directory with mixed file types' do
497
+ test_dir = create_test_dir
498
+ # Copy spritesheets and create non-PNG files
499
+ sprite1_path = File.join(test_dir, 'sprite1.png')
500
+ sprite2_path = File.join(test_dir, 'sprite2.png')
501
+ FileUtils.cp(spritesheet1, sprite1_path)
502
+ FileUtils.cp(spritesheet2, sprite2_path)
503
+ File.write(File.join(test_dir, 'readme.txt'), 'test')
504
+ File.write(File.join(test_dir, 'data.json'), '{}')
505
+
506
+ # Mock metadata reading
507
+ allow(RubySpriter::MetadataManager).to receive(:read).and_call_original
508
+ allow(RubySpriter::MetadataManager).to receive(:read).with(sprite1_path).and_return(
509
+ { columns: 2, rows: 2, frames: 4, version: '0.6' }
510
+ )
511
+ allow(RubySpriter::MetadataManager).to receive(:read).with(sprite2_path).and_return(
512
+ { columns: 2, rows: 3, frames: 6, version: '0.6' }
513
+ )
514
+
515
+ found_files = consolidator.find_spritesheets_in_directory(test_dir)
516
+
517
+ expect(found_files.length).to eq(2)
518
+ expect(found_files).to all(end_with('.png'))
519
+ end
520
+
521
+ it 'requires at least 2 spritesheets' do
522
+ test_dir = create_test_dir
523
+ # Copy only one spritesheet
524
+ sprite1_path = File.join(test_dir, 'sprite1.png')
525
+ FileUtils.cp(spritesheet1, sprite1_path)
526
+
527
+ # Mock metadata reading
528
+ allow(RubySpriter::MetadataManager).to receive(:read).and_call_original
529
+ allow(RubySpriter::MetadataManager).to receive(:read).with(sprite1_path).and_return(
530
+ { columns: 2, rows: 2, frames: 4, version: '0.6' }
531
+ )
532
+
533
+ expect {
534
+ consolidator.find_spritesheets_in_directory(test_dir)
535
+ }.to raise_error(RubySpriter::ValidationError, /Found only 1 spritesheet, need at least 2/)
536
+ end
537
+ end
375
538
  end
@@ -71,7 +71,7 @@ RSpec.describe RubySpriter::Platform do
71
71
  describe '.imagemagick_identify_cmd' do
72
72
  it 'returns appropriate command for platform' do
73
73
  cmd = described_class.imagemagick_identify_cmd
74
-
74
+
75
75
  if described_class.windows?
76
76
  expect(cmd).to eq('magick identify')
77
77
  else
@@ -79,4 +79,14 @@ RSpec.describe RubySpriter::Platform do
79
79
  end
80
80
  end
81
81
  end
82
+
83
+ describe '.detect_gimp_version' do
84
+ it 'detects GIMP 3.x version from command output' do
85
+ gimp3_output = "GNU Image Manipulation Program version 3.0.0"
86
+ version = described_class.detect_gimp_version(gimp3_output)
87
+ expect(version[:major]).to eq(3)
88
+ expect(version[:minor]).to eq(0)
89
+ expect(version[:full]).to eq('3.0.0')
90
+ end
91
+ end
82
92
  end