ruby_spriter 0.6.5 → 0.6.7

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.
@@ -77,6 +77,21 @@ RSpec.describe RubySpriter::CLI do
77
77
  expect { described_class.start(['-h']) }.to output(/Usage: ruby_spriter/).to_stdout
78
78
  end.to raise_error(SystemExit)
79
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
80
95
  end
81
96
 
82
97
  describe '--version flag' do
@@ -155,6 +170,32 @@ RSpec.describe RubySpriter::CLI do
155
170
  end.to raise_error(SystemExit)
156
171
  end
157
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
158
199
  end
159
200
 
160
201
  describe '--image flag' do
@@ -425,12 +466,366 @@ RSpec.describe RubySpriter::CLI do
425
466
 
426
467
  described_class.start(['--image', fixture_with_meta, '--output', 'custom_output.png'])
427
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
428
688
  end
429
689
  end
430
690
 
431
691
  describe '--video flag' do
432
692
  let(:fixture_video) { File.join(__dir__, '..', 'fixtures', 'test_video.mp4') }
433
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
+ it 'shows image mode help with --help' do
769
+ output = StringIO.new
770
+ $stdout = output
771
+
772
+ begin
773
+ described_class.start(['--image', '--help'])
774
+ rescue SystemExit
775
+ # Expected
776
+ ensure
777
+ $stdout = STDOUT
778
+ end
779
+
780
+ expect(output.string).to include('Image Mode')
781
+ end
782
+
783
+ it 'shows consolidate mode help with --help' do
784
+ output = StringIO.new
785
+ $stdout = output
786
+
787
+ begin
788
+ described_class.start(['--consolidate', '--help'])
789
+ rescue SystemExit
790
+ # Expected
791
+ ensure
792
+ $stdout = STDOUT
793
+ end
794
+
795
+ expect(output.string).to include('Consolidate Mode')
796
+ end
797
+
798
+ it 'shows batch mode help with --help' do
799
+ output = StringIO.new
800
+ $stdout = output
801
+
802
+ begin
803
+ described_class.start(['--batch', '--help'])
804
+ rescue SystemExit
805
+ # Expected
806
+ ensure
807
+ $stdout = STDOUT
808
+ end
809
+
810
+ expect(output.string).to include('Batch Mode')
811
+ end
812
+
813
+ it 'shows split mode help with --help' do
814
+ output = StringIO.new
815
+ $stdout = output
816
+
817
+ begin
818
+ described_class.start(['--split', '--help'])
819
+ rescue SystemExit
820
+ # Expected
821
+ ensure
822
+ $stdout = STDOUT
823
+ end
824
+
825
+ expect(output.string).to include('Split Mode')
826
+ end
827
+ end
828
+
434
829
  describe 'argument parsing' do
435
830
  it 'sets video option with --video flag' do
436
831
  processor_double = instance_double(RubySpriter::Processor)
@@ -763,6 +1158,19 @@ RSpec.describe RubySpriter::CLI do
763
1158
 
764
1159
  described_class.start(['--video', fixture_video, '--output', 'custom_spritesheet.png'])
765
1160
  end
1161
+
1162
+ it 'works with --save-frames option' do
1163
+ processor_double = instance_double(RubySpriter::Processor)
1164
+ allow(processor_double).to receive(:run)
1165
+
1166
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1167
+ expect(options[:video]).to eq(fixture_video)
1168
+ expect(options[:save_frames]).to eq(true)
1169
+ processor_double
1170
+ end
1171
+
1172
+ described_class.start(['--video', fixture_video, '--save-frames'])
1173
+ end
766
1174
  end
767
1175
 
768
1176
  describe 'preset configurations' do
@@ -1117,6 +1525,176 @@ RSpec.describe RubySpriter::CLI do
1117
1525
  '--debug'
1118
1526
  ])
1119
1527
  end
1528
+
1529
+ it 'works with --overwrite option' do
1530
+ processor_double = instance_double(RubySpriter::Processor)
1531
+ allow(processor_double).to receive(:run)
1532
+
1533
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1534
+ expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
1535
+ expect(options[:overwrite]).to eq(true)
1536
+ processor_double
1537
+ end
1538
+
1539
+ described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--overwrite'])
1540
+ end
1541
+ end
1542
+
1543
+ describe 'default output filename behavior' do
1544
+ it 'generates consolidated_spritesheet.png when no --output specified' do
1545
+ # Mock all the dependencies
1546
+ allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
1547
+ allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
1548
+ expect(path).to eq('consolidated_spritesheet.png')
1549
+ expect(overwrite).to eq(false)
1550
+ 'consolidated_spritesheet.png'
1551
+ end
1552
+
1553
+ consolidator_double = instance_double(RubySpriter::Consolidator)
1554
+ allow(RubySpriter::Consolidator).to receive(:new).and_return(consolidator_double)
1555
+ allow(consolidator_double).to receive(:consolidate).and_return({
1556
+ output_file: 'consolidated_spritesheet.png',
1557
+ columns: 2,
1558
+ rows: 4,
1559
+ frames: 8
1560
+ })
1561
+
1562
+ processor = RubySpriter::Processor.new(
1563
+ consolidate_mode: true,
1564
+ consolidate: [spritesheet_4x2, spritesheet_6x2],
1565
+ overwrite: false
1566
+ )
1567
+
1568
+ allow(processor).to receive(:check_dependencies!)
1569
+ allow(processor).to receive(:setup_temp_directory)
1570
+ allow(processor).to receive(:cleanup)
1571
+
1572
+ # Capture output to suppress console messages
1573
+ expect { processor.run }.to output(/SUCCESS/).to_stdout
1574
+ end
1575
+
1576
+ it 'respects --overwrite flag with default filename' do
1577
+ # Mock all the dependencies
1578
+ allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
1579
+ allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
1580
+ expect(path).to eq('consolidated_spritesheet.png')
1581
+ expect(overwrite).to eq(true)
1582
+ 'consolidated_spritesheet.png'
1583
+ end
1584
+
1585
+ consolidator_double = instance_double(RubySpriter::Consolidator)
1586
+ allow(RubySpriter::Consolidator).to receive(:new).and_return(consolidator_double)
1587
+ allow(consolidator_double).to receive(:consolidate).and_return({
1588
+ output_file: 'consolidated_spritesheet.png',
1589
+ columns: 2,
1590
+ rows: 4,
1591
+ frames: 8
1592
+ })
1593
+
1594
+ processor = RubySpriter::Processor.new(
1595
+ consolidate_mode: true,
1596
+ consolidate: [spritesheet_4x2, spritesheet_6x2],
1597
+ overwrite: true
1598
+ )
1599
+
1600
+ allow(processor).to receive(:check_dependencies!)
1601
+ allow(processor).to receive(:setup_temp_directory)
1602
+ allow(processor).to receive(:cleanup)
1603
+
1604
+ # Capture output to suppress console messages
1605
+ expect { processor.run }.to output(/SUCCESS/).to_stdout
1606
+ end
1607
+ end
1608
+
1609
+ describe 'directory-based consolidation' do
1610
+ let(:test_dir) { File.join(@test_dir, 'consolidate_dir') }
1611
+
1612
+ before do
1613
+ FileUtils.mkdir_p(test_dir)
1614
+ # Copy fixture spritesheets to test directory
1615
+ FileUtils.cp(spritesheet_4x2, File.join(test_dir, 'sprite1.png'))
1616
+ FileUtils.cp(spritesheet_6x2, File.join(test_dir, 'sprite2.png'))
1617
+ end
1618
+
1619
+ it 'accepts --dir option with --consolidate' do
1620
+ processor_double = instance_double(RubySpriter::Processor)
1621
+ allow(processor_double).to receive(:run)
1622
+
1623
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1624
+ expect(options[:consolidate]).to be_nil
1625
+ expect(options[:dir]).to eq(test_dir)
1626
+ processor_double
1627
+ end
1628
+
1629
+ described_class.start(['--consolidate', '--dir', test_dir])
1630
+ end
1631
+
1632
+ it 'validates directory exists' do
1633
+ expect do
1634
+ described_class.start(['--consolidate', '--dir', 'nonexistent_directory'])
1635
+ end.to raise_error(RubySpriter::ValidationError, /Directory not found/)
1636
+ end
1637
+
1638
+ it 'cannot use --dir with comma-separated file list' do
1639
+ expect do
1640
+ described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--dir', test_dir])
1641
+ end.to raise_error(RubySpriter::ValidationError, /Cannot use --dir with comma-separated file list/)
1642
+ end
1643
+
1644
+ it 'works with --output option' do
1645
+ processor_double = instance_double(RubySpriter::Processor)
1646
+ allow(processor_double).to receive(:run)
1647
+
1648
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1649
+ expect(options[:dir]).to eq(test_dir)
1650
+ expect(options[:output]).to eq('custom_output.png')
1651
+ processor_double
1652
+ end
1653
+
1654
+ described_class.start(['--consolidate', '--dir', test_dir, '--output', 'custom_output.png'])
1655
+ end
1656
+
1657
+ it 'works with --outputdir option' do
1658
+ output_dir = File.join(@test_dir, 'output')
1659
+ FileUtils.mkdir_p(output_dir)
1660
+
1661
+ processor_double = instance_double(RubySpriter::Processor)
1662
+ allow(processor_double).to receive(:run)
1663
+
1664
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1665
+ expect(options[:dir]).to eq(test_dir)
1666
+ expect(options[:outputdir]).to eq(output_dir)
1667
+ processor_double
1668
+ end
1669
+
1670
+ described_class.start(['--consolidate', '--dir', test_dir, '--outputdir', output_dir])
1671
+ end
1672
+
1673
+ it 'works with --overwrite option' do
1674
+ processor_double = instance_double(RubySpriter::Processor)
1675
+ allow(processor_double).to receive(:run)
1676
+
1677
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1678
+ expect(options[:dir]).to eq(test_dir)
1679
+ expect(options[:overwrite]).to eq(true)
1680
+ processor_double
1681
+ end
1682
+
1683
+ described_class.start(['--consolidate', '--dir', test_dir, '--overwrite'])
1684
+ end
1685
+
1686
+ it 'works with --max-compress option' do
1687
+ processor_double = instance_double(RubySpriter::Processor)
1688
+ allow(processor_double).to receive(:run)
1689
+
1690
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1691
+ expect(options[:dir]).to eq(test_dir)
1692
+ expect(options[:max_compress]).to eq(true)
1693
+ processor_double
1694
+ end
1695
+
1696
+ described_class.start(['--consolidate', '--dir', test_dir, '--max-compress'])
1697
+ end
1120
1698
  end
1121
1699
  end
1122
1700
 
@@ -1138,5 +1716,177 @@ RSpec.describe RubySpriter::CLI do
1138
1716
  end.to raise_error(SystemExit)
1139
1717
  end
1140
1718
  end
1719
+
1720
+ describe '--split option' do
1721
+ it 'parses split option with R:C format' do
1722
+ processor_double = instance_double(RubySpriter::Processor)
1723
+ allow(processor_double).to receive(:run)
1724
+
1725
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1726
+ expect(options[:split]).to eq('4:4')
1727
+ processor_double
1728
+ end
1729
+
1730
+ described_class.start(['--image', 'test.png', '--split', '4:4'])
1731
+ end
1732
+ end
1733
+
1734
+ describe '--override-md option' do
1735
+ it 'sets override_md option to true' do
1736
+ processor_double = instance_double(RubySpriter::Processor)
1737
+ allow(processor_double).to receive(:run)
1738
+
1739
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1740
+ expect(options[:override_md]).to eq(true)
1741
+ processor_double
1742
+ end
1743
+
1744
+ described_class.start(['--image', 'test.png', '--split', '4:4', '--override-md'])
1745
+ end
1746
+ end
1747
+
1748
+ describe '--extract option' do
1749
+ it 'parses extract option with comma-separated frame numbers' do
1750
+ processor_double = instance_double(RubySpriter::Processor)
1751
+ allow(processor_double).to receive(:run)
1752
+
1753
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1754
+ expect(options[:extract]).to eq('1,2,4,5,8')
1755
+ processor_double
1756
+ end
1757
+
1758
+ described_class.start(['--image', 'test.png', '--extract', '1,2,4,5,8'])
1759
+ end
1760
+
1761
+ it 'allows duplicate frame numbers' do
1762
+ processor_double = instance_double(RubySpriter::Processor)
1763
+ allow(processor_double).to receive(:run)
1764
+
1765
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1766
+ expect(options[:extract]).to eq('1,1,2,2,3,3')
1767
+ processor_double
1768
+ end
1769
+
1770
+ described_class.start(['--image', 'test.png', '--extract', '1,1,2,2,3,3'])
1771
+ end
1772
+
1773
+ it 'cannot be used with --split' do
1774
+ expect do
1775
+ described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--split', '4:4'])
1776
+ end.to raise_error(RubySpriter::ValidationError, /--extract and --split are mutually exclusive/)
1777
+ end
1778
+ end
1779
+
1780
+ describe '--columns option' do
1781
+ it 'parses columns option for extraction grid' do
1782
+ processor_double = instance_double(RubySpriter::Processor)
1783
+ allow(processor_double).to receive(:run)
1784
+
1785
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1786
+ expect(options[:columns]).to eq(3)
1787
+ processor_double
1788
+ end
1789
+
1790
+ described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--columns', '3'])
1791
+ end
1792
+
1793
+ it 'works without --extract (for future use)' do
1794
+ processor_double = instance_double(RubySpriter::Processor)
1795
+ allow(processor_double).to receive(:run)
1796
+
1797
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1798
+ expect(options[:columns]).to eq(5)
1799
+ processor_double
1800
+ end
1801
+
1802
+ described_class.start(['--image', 'test.png', '--columns', '5'])
1803
+ end
1804
+ end
1805
+
1806
+ describe '--save-frames option' do
1807
+ it 'sets save_frames option to true' do
1808
+ processor_double = instance_double(RubySpriter::Processor)
1809
+ allow(processor_double).to receive(:run)
1810
+
1811
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1812
+ expect(options[:save_frames]).to eq(true)
1813
+ processor_double
1814
+ end
1815
+
1816
+ described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--save-frames'])
1817
+ end
1818
+
1819
+ it 'can be used without --extract' do
1820
+ processor_double = instance_double(RubySpriter::Processor)
1821
+ allow(processor_double).to receive(:run)
1822
+
1823
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1824
+ expect(options[:save_frames]).to eq(true)
1825
+ processor_double
1826
+ end
1827
+
1828
+ described_class.start(['--image', 'test.png', '--split', '4:4', '--save-frames'])
1829
+ end
1830
+ end
1831
+
1832
+ describe '--add-meta option' do
1833
+ it 'parses add-meta option with R:C format' do
1834
+ processor_double = instance_double(RubySpriter::Processor)
1835
+ allow(processor_double).to receive(:run)
1836
+
1837
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1838
+ expect(options[:add_meta]).to eq('4:4')
1839
+ processor_double
1840
+ end
1841
+
1842
+ described_class.start(['--image', 'test.png', '--add-meta', '4:4'])
1843
+ end
1844
+
1845
+ it 'cannot be combined with --scale' do
1846
+ expect do
1847
+ described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--scale', '50'])
1848
+ end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
1849
+ end
1850
+
1851
+ it 'cannot be combined with --remove-bg' do
1852
+ expect do
1853
+ described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--remove-bg'])
1854
+ end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
1855
+ end
1856
+
1857
+ it 'cannot be combined with --sharpen' do
1858
+ expect do
1859
+ described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--sharpen'])
1860
+ end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
1861
+ end
1862
+ end
1863
+
1864
+ describe '--overwrite-meta option' do
1865
+ it 'sets overwrite_meta option to true' do
1866
+ processor_double = instance_double(RubySpriter::Processor)
1867
+ allow(processor_double).to receive(:run)
1868
+
1869
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1870
+ expect(options[:overwrite_meta]).to eq(true)
1871
+ processor_double
1872
+ end
1873
+
1874
+ described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--overwrite-meta'])
1875
+ end
1876
+ end
1877
+
1878
+ describe '--frames option for partial grids' do
1879
+ it 'parses frames option with integer value' do
1880
+ processor_double = instance_double(RubySpriter::Processor)
1881
+ allow(processor_double).to receive(:run)
1882
+
1883
+ allow(RubySpriter::Processor).to receive(:new) do |options|
1884
+ expect(options[:frame_count]).to eq(14)
1885
+ processor_double
1886
+ end
1887
+
1888
+ described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--frames', '14'])
1889
+ end
1890
+ end
1141
1891
  end
1142
1892
  end