discourse_image_optim 0.24.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.appveyor.yml +46 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +110 -0
  5. data/.travis.yml +42 -0
  6. data/CHANGELOG.markdown +316 -0
  7. data/CONTRIBUTING.markdown +11 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.markdown +358 -0
  11. data/Vagrantfile +38 -0
  12. data/bin/image_optim +28 -0
  13. data/image_optim.gemspec +34 -0
  14. data/lib/image_optim.rb +267 -0
  15. data/lib/image_optim/bin_resolver.rb +142 -0
  16. data/lib/image_optim/bin_resolver/bin.rb +115 -0
  17. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  18. data/lib/image_optim/bin_resolver/error.rb +6 -0
  19. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  20. data/lib/image_optim/cache.rb +72 -0
  21. data/lib/image_optim/cache_path.rb +16 -0
  22. data/lib/image_optim/cmd.rb +122 -0
  23. data/lib/image_optim/config.rb +219 -0
  24. data/lib/image_optim/configuration_error.rb +3 -0
  25. data/lib/image_optim/handler.rb +57 -0
  26. data/lib/image_optim/hash_helpers.rb +45 -0
  27. data/lib/image_optim/image_meta.rb +20 -0
  28. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  29. data/lib/image_optim/optimized_path.rb +25 -0
  30. data/lib/image_optim/option_definition.rb +38 -0
  31. data/lib/image_optim/option_helpers.rb +17 -0
  32. data/lib/image_optim/path.rb +70 -0
  33. data/lib/image_optim/runner.rb +139 -0
  34. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  35. data/lib/image_optim/runner/option_parser.rb +246 -0
  36. data/lib/image_optim/space.rb +29 -0
  37. data/lib/image_optim/true_false_nil.rb +16 -0
  38. data/lib/image_optim/worker.rb +170 -0
  39. data/lib/image_optim/worker/advpng.rb +37 -0
  40. data/lib/image_optim/worker/class_methods.rb +107 -0
  41. data/lib/image_optim/worker/gifsicle.rb +65 -0
  42. data/lib/image_optim/worker/jhead.rb +47 -0
  43. data/lib/image_optim/worker/jpegoptim.rb +63 -0
  44. data/lib/image_optim/worker/jpegrecompress.rb +49 -0
  45. data/lib/image_optim/worker/jpegtran.rb +48 -0
  46. data/lib/image_optim/worker/optipng.rb +53 -0
  47. data/lib/image_optim/worker/pngcrush.rb +56 -0
  48. data/lib/image_optim/worker/pngout.rb +40 -0
  49. data/lib/image_optim/worker/pngquant.rb +61 -0
  50. data/lib/image_optim/worker/svgo.rb +34 -0
  51. data/script/template/jquery-2.1.3.min.js +4 -0
  52. data/script/template/sortable-0.6.0.min.js +2 -0
  53. data/script/template/worker_analysis.erb +254 -0
  54. data/script/update_worker_options_in_readme +59 -0
  55. data/script/worker_analysis +589 -0
  56. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  57. data/spec/image_optim/bin_resolver/simple_version_spec.rb +65 -0
  58. data/spec/image_optim/bin_resolver_spec.rb +290 -0
  59. data/spec/image_optim/cache_path_spec.rb +57 -0
  60. data/spec/image_optim/cache_spec.rb +162 -0
  61. data/spec/image_optim/cmd_spec.rb +93 -0
  62. data/spec/image_optim/config_spec.rb +254 -0
  63. data/spec/image_optim/handler_spec.rb +90 -0
  64. data/spec/image_optim/hash_helpers_spec.rb +74 -0
  65. data/spec/image_optim/image_meta_spec.rb +61 -0
  66. data/spec/image_optim/optimized_path_spec.rb +58 -0
  67. data/spec/image_optim/option_definition_spec.rb +138 -0
  68. data/spec/image_optim/option_helpers_spec.rb +25 -0
  69. data/spec/image_optim/path_spec.rb +103 -0
  70. data/spec/image_optim/runner/glob_helpers_spec.rb +21 -0
  71. data/spec/image_optim/runner/option_parser_spec.rb +105 -0
  72. data/spec/image_optim/space_spec.rb +23 -0
  73. data/spec/image_optim/worker/optipng_spec.rb +102 -0
  74. data/spec/image_optim/worker/pngquant_spec.rb +67 -0
  75. data/spec/image_optim/worker_spec.rb +303 -0
  76. data/spec/image_optim_spec.rb +259 -0
  77. data/spec/images/broken_jpeg +1 -0
  78. data/spec/images/comparison.png +0 -0
  79. data/spec/images/decompressed.jpeg +0 -0
  80. data/spec/images/icecream.gif +0 -0
  81. data/spec/images/image.jpg +0 -0
  82. data/spec/images/invisiblepixels/generate +24 -0
  83. data/spec/images/invisiblepixels/image.png +0 -0
  84. data/spec/images/lena.jpg +0 -0
  85. data/spec/images/orient/0.jpg +0 -0
  86. data/spec/images/orient/1.jpg +0 -0
  87. data/spec/images/orient/2.jpg +0 -0
  88. data/spec/images/orient/3.jpg +0 -0
  89. data/spec/images/orient/4.jpg +0 -0
  90. data/spec/images/orient/5.jpg +0 -0
  91. data/spec/images/orient/6.jpg +0 -0
  92. data/spec/images/orient/7.jpg +0 -0
  93. data/spec/images/orient/8.jpg +0 -0
  94. data/spec/images/orient/generate +23 -0
  95. data/spec/images/orient/original.jpg +0 -0
  96. data/spec/images/quant/64.png +0 -0
  97. data/spec/images/quant/generate +25 -0
  98. data/spec/images/rails.png +0 -0
  99. data/spec/images/test.svg +3 -0
  100. data/spec/images/transparency1.png +0 -0
  101. data/spec/images/transparency2.png +0 -0
  102. data/spec/images/vergroessert.jpg +0 -0
  103. data/spec/spec_helper.rb +93 -0
  104. metadata +281 -0
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/path'
3
+ require 'tempfile'
4
+
5
+ describe ImageOptim::Path do
6
+ include CapabilityCheckHelpers
7
+
8
+ before do
9
+ stub_const('Path', ImageOptim::Path)
10
+ end
11
+
12
+ describe '.convert' do
13
+ it 'returns Path for string' do
14
+ path = 'a'
15
+
16
+ expect(Path.convert(path)).to be_a(Path)
17
+ expect(Path.convert(path)).to eq(Path.new(path))
18
+
19
+ expect(Path.convert(path)).not_to eq(path)
20
+ expect(Path.convert(path)).not_to be(path)
21
+ end
22
+
23
+ it 'returns Path for Pathname' do
24
+ pathname = Pathname.new('a')
25
+
26
+ expect(Path.convert(pathname)).to be_a(Path)
27
+ expect(Path.convert(pathname)).to eq(Path.new(pathname))
28
+
29
+ expect(Path.convert(pathname)).to eq(pathname)
30
+ expect(Path.convert(pathname)).not_to be(pathname)
31
+ end
32
+
33
+ it 'returns same instance for Path' do
34
+ image_path = Path.new('a')
35
+
36
+ expect(Path.convert(image_path)).to be_a(Path)
37
+ expect(Path.convert(image_path)).to eq(Path.new(image_path))
38
+
39
+ expect(Path.convert(image_path)).to eq(image_path)
40
+ expect(Path.convert(image_path)).to be(image_path)
41
+ end
42
+ end
43
+
44
+ describe '#binread' do
45
+ it 'reads binary data' do
46
+ data = (0..255).to_a.pack('c*')
47
+
48
+ path = Path.temp_file_path
49
+ path.binwrite(data)
50
+
51
+ expect(path.binread).to eq(data)
52
+ if ''.respond_to?(:encoding)
53
+ expect(path.binread.encoding).to eq(Encoding.find('ASCII-8BIT'))
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#replace' do
59
+ let(:src){ Path.temp_file_path }
60
+ let(:dst){ Path.temp_file_path }
61
+
62
+ it 'moves data to destination' do
63
+ src.write('src')
64
+
65
+ src.replace(dst)
66
+
67
+ expect(dst.read).to eq('src')
68
+ end
69
+
70
+ it 'removes original file' do
71
+ src.replace(dst)
72
+
73
+ expect(src).to_not exist
74
+ end
75
+
76
+ it 'preserves attributes of destination file' do
77
+ skip 'full file modes are not support' unless any_file_modes_allowed?
78
+ mode = 0o666
79
+
80
+ dst.chmod(mode)
81
+
82
+ src.replace(dst)
83
+
84
+ got = dst.stat.mode & 0o777
85
+ expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
86
+ end
87
+
88
+ it 'does not preserve mtime of destination file' do
89
+ time = src.mtime
90
+
91
+ dst.utime(time - 1000, time - 1000)
92
+
93
+ src.replace(dst)
94
+
95
+ expect(dst.mtime).to be >= time
96
+ end
97
+
98
+ it 'changes inode of destination' do
99
+ skip 'inodes are not supported' unless inodes_supported?
100
+ expect{ src.replace(dst) }.to change{ dst.stat.ino }
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/runner/glob_helpers'
3
+
4
+ describe ImageOptim::Runner::GlobHelpers do
5
+ describe '.expand_braces' do
6
+ {
7
+ 'hello.world' => %w[hello.world],
8
+ '{hello,.world}' => %w[hello .world],
9
+ 'hello{.,!}world' => %w[hello.world hello!world],
10
+ '{a,b},{c,d}' => %w[a,c b,c a,d b,d],
11
+ '{{a,b},{c,d}}' => %w[a b c d],
12
+ '{a,b,{c,d}}' => %w[a b c d],
13
+ '{\{a,b\},\{c,d\}}' => %w[\\{a b\\} \\{c d\\}],
14
+ 'test{ing,}' => %w[testing test],
15
+ }.each do |glob, expected|
16
+ it "expands #{glob}" do
17
+ expect(described_class.expand_braces(glob)).to match_array(expected)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/runner/option_parser'
3
+
4
+ describe ImageOptim::Runner::OptionParser do
5
+ before do
6
+ stub_const('OptionParser', ImageOptim::Runner::OptionParser)
7
+ end
8
+
9
+ def exit_with_status(status)
10
+ raise_error(SystemExit) do |e|
11
+ expect(e.status).to eq(status)
12
+ end
13
+ end
14
+
15
+ describe '.parse!' do
16
+ it 'returns empty hash for arguments without options' do
17
+ args = %w[foo bar]
18
+ expect(OptionParser.parse!(args)).to eq({})
19
+ expect(args).to eq(%w[foo bar])
20
+ end
21
+
22
+ it 'removes options from arguments' do
23
+ args = %w[-r foo bar]
24
+ OptionParser.parse!(args)
25
+ expect(args).to eq(%w[foo bar])
26
+ end
27
+
28
+ it 'stops parsing options after --' do
29
+ args = %w[-- -r foo bar]
30
+ OptionParser.parse!(args)
31
+ expect(args).to eq(%w[-r foo bar])
32
+ end
33
+
34
+ describe 'boolean option recursive' do
35
+ %w[-r -R --recursive].each do |flag|
36
+ it "is parsed from #{flag}" do
37
+ args = %W[#{flag} foo bar]
38
+ expect(OptionParser.parse!(args)).to eq(:recursive => true)
39
+ expect(args).to eq(%w[foo bar])
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'numeric option threads' do
45
+ it 'is parsed with space separator' do
46
+ args = %w[--threads 616 foo bar]
47
+ expect(OptionParser.parse!(args)).to eq(:threads => 616)
48
+ expect(args).to eq(%w[foo bar])
49
+ end
50
+
51
+ it 'is parsed with equal separator' do
52
+ args = %w[--threads=616 foo bar]
53
+ expect(OptionParser.parse!(args)).to eq(:threads => 616)
54
+ expect(args).to eq(%w[foo bar])
55
+ end
56
+
57
+ it 'is parsed with no- prefix' do
58
+ args = %w[--no-threads 616 foo bar]
59
+ expect(OptionParser.parse!(args)).to eq(:threads => false)
60
+ expect(args).to eq(%w[616 foo bar])
61
+ end
62
+ end
63
+
64
+ describe 'help option' do
65
+ it 'prints help text to stdout and exits' do
66
+ parser = OptionParser.new({})
67
+ expect(OptionParser).to receive(:new).and_return(parser)
68
+
69
+ help = double
70
+ expect(parser).to receive(:help).and_return(help)
71
+
72
+ expect do
73
+ OptionParser.parse!(%w[--help])
74
+ end.to exit_with_status(0) &
75
+ output("#{help}\n").to_stdout
76
+ end
77
+ end
78
+
79
+ describe 'wrong option' do
80
+ it 'prints help text to stdout and exits' do
81
+ parser = OptionParser.new({})
82
+ expect(OptionParser).to receive(:new).and_return(parser)
83
+
84
+ help = double
85
+ expect(parser).to receive(:help).and_return(help)
86
+
87
+ expect do
88
+ OptionParser.parse!(%w[--unknown-option])
89
+ end.to exit_with_status(1) &
90
+ output("invalid option: --unknown-option\n\n#{help}\n").to_stderr
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '#help' do
96
+ it 'returns wrapped text' do
97
+ parser = OptionParser.new({})
98
+
99
+ allow(parser).to receive(:terminal_columns).and_return(80)
100
+
101
+ expect(parser.help.split("\n")).
102
+ to all(satisfy{ |line| line.length <= 80 })
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/space'
3
+
4
+ describe ImageOptim::Space do
5
+ describe '.space' do
6
+ {
7
+ 0 => ' ',
8
+ 1 => ' 1B',
9
+ 10 => ' 10B',
10
+ 100 => ' 100B',
11
+ 1_000 => ' 1000B',
12
+ 10_000 => ' 9.8K',
13
+ 100_000 => ' 97.7K',
14
+ 1_000_000 => '976.6K',
15
+ 10_000_000 => ' 9.5M',
16
+ 100_000_000 => ' 95.4M',
17
+ }.each do |size, space|
18
+ it "converts #{size} to #{space}" do
19
+ expect(described_class.space(size)).to eq(space)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/worker/optipng'
3
+ require 'image_optim/path'
4
+
5
+ describe ImageOptim::Worker::Optipng do
6
+ describe 'strip option' do
7
+ subject{ described_class.new(ImageOptim.new, options) }
8
+
9
+ let(:options){ {} }
10
+ let(:optipng_version){ '0.7' }
11
+ let(:src){ instance_double(ImageOptim::Path, :copy => nil) }
12
+ let(:dst){ instance_double(ImageOptim::Path) }
13
+
14
+ before do
15
+ optipng_bin = instance_double(ImageOptim::BinResolver::Bin)
16
+ allow(subject).to receive(:resolve_bin!).
17
+ with(:optipng).and_return(optipng_bin)
18
+ allow(optipng_bin).to receive(:version).
19
+ and_return(ImageOptim::BinResolver::SimpleVersion.new(optipng_version))
20
+
21
+ allow(subject).to receive(:optimized?)
22
+ end
23
+
24
+ context 'by default' do
25
+ it 'should add -strip all to arguments' do
26
+ expect(subject).to receive(:execute) do |_bin, *args|
27
+ expect(args.join(' ')).to match(/(^| )-strip all($| )/)
28
+ end
29
+
30
+ subject.optimize(src, dst)
31
+ end
32
+ end
33
+
34
+ context 'when strip is disabled' do
35
+ let(:options){ {:strip => false} }
36
+
37
+ it 'should not add -strip all to arguments' do
38
+ expect(subject).to receive(:execute) do |_bin, *args|
39
+ expect(args.join(' ')).not_to match(/(^| )-strip all($| )/)
40
+ end
41
+
42
+ subject.optimize(src, dst)
43
+ end
44
+ end
45
+
46
+ context 'when optipng version is < 0.7' do
47
+ let(:optipng_version){ '0.6.999' }
48
+
49
+ it 'should not add -strip all to arguments' do
50
+ expect(subject).to receive(:execute) do |_bin, *args|
51
+ expect(args.join(' ')).not_to match(/(^| )-strip all($| )/)
52
+ end
53
+
54
+ subject.optimize(src, dst)
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#optimized?' do
60
+ let(:src){ instance_double(ImageOptim::Path, src_options) }
61
+ let(:dst){ instance_double(ImageOptim::Path, dst_options) }
62
+ let(:src_options){ {:size => 10} }
63
+ let(:dst_options){ {:size? => 9} }
64
+ let(:instance){ described_class.new(ImageOptim.new, instance_options) }
65
+ let(:instance_options){ {} }
66
+
67
+ subject{ instance.optimized?(src, dst) }
68
+
69
+ context 'when interlace option is enabled' do
70
+ let(:instance_options){ {:interlace => true} }
71
+
72
+ context 'when dst is empty' do
73
+ let(:dst_options){ {:size? => nil} }
74
+ it{ is_expected.to be_falsy }
75
+ end
76
+
77
+ context 'when dst is not empty' do
78
+ let(:dst_options){ {:size? => 20} }
79
+ it{ is_expected.to be_truthy }
80
+ end
81
+ end
82
+
83
+ context 'when interlace option is disabled' do
84
+ let(:instance_options){ {:interlace => false} }
85
+
86
+ context 'when dst is empty' do
87
+ let(:dst_options){ {:size? => nil} }
88
+ it{ is_expected.to be_falsy }
89
+ end
90
+
91
+ context 'when dst is greater than or equal to src' do
92
+ let(:dst_options){ {:size? => 10} }
93
+ it{ is_expected.to be_falsy }
94
+ end
95
+
96
+ context 'when dst is less than src' do
97
+ let(:dst_options){ {:size? => 9} }
98
+ it{ is_expected.to be_truthy }
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/worker/pngquant'
3
+
4
+ describe ImageOptim::Worker::Pngquant do
5
+ describe 'quality option' do
6
+ describe 'default' do
7
+ subject{ described_class::QUALITY_OPTION.default }
8
+
9
+ it{ is_expected.to match(/100\.\.100.*0\.\.100/) }
10
+ end
11
+
12
+ describe 'value' do
13
+ let(:subject){ described_class.new(ImageOptim.new, options).quality }
14
+
15
+ context 'when lossy not allowed' do
16
+ context 'by default' do
17
+ let(:options){ {} }
18
+
19
+ it{ is_expected.to eq(100..100) }
20
+ end
21
+
22
+ context 'when value is passed through options' do
23
+ let(:options){ {:quality => 10..90} }
24
+
25
+ it 'warns and keeps default' do
26
+ expect_any_instance_of(described_class).
27
+ to receive(:warn).with(/ignored in lossless mode/)
28
+ is_expected.to eq(100..100)
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'when lossy allowed' do
34
+ context 'by default' do
35
+ let(:options){ {:allow_lossy => true} }
36
+
37
+ it{ is_expected.to eq(0..100) }
38
+ end
39
+
40
+ context 'when value is passed through options' do
41
+ let(:options){ {:allow_lossy => true, :quality => 10..90} }
42
+
43
+ it 'sets the value without warning' do
44
+ expect_any_instance_of(described_class).not_to receive(:warn)
45
+ is_expected.to eq(10..90)
46
+ end
47
+ end
48
+
49
+ context 'when passed range begin is less than 0' do
50
+ let(:options){ {:allow_lossy => true, :quality => -50..50} }
51
+
52
+ it 'sets begin to 0' do
53
+ is_expected.to eq(0..50)
54
+ end
55
+ end
56
+
57
+ context 'when passed range end is more than 100' do
58
+ let(:options){ {:allow_lossy => true, :quality => 50..150} }
59
+
60
+ it 'sets end to 100' do
61
+ is_expected.to eq(50..100)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,303 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/worker'
3
+ require 'image_optim/bin_resolver'
4
+
5
+ describe ImageOptim::Worker do
6
+ before do
7
+ stub_const('Worker', ImageOptim::Worker)
8
+ stub_const('BinResolver', ImageOptim::BinResolver)
9
+
10
+ # don't add to list of wokers
11
+ allow(ImageOptim::Worker).to receive(:inherited)
12
+ end
13
+
14
+ describe '#initialize' do
15
+ it 'expects first argument to be an instanace of ImageOptim' do
16
+ expect do
17
+ Worker.new(double)
18
+ end.to raise_error ArgumentError
19
+ end
20
+ end
21
+
22
+ describe '#options' do
23
+ it 'returns a Hash with options' do
24
+ worker_class = Class.new(Worker) do
25
+ option(:one, 1, 'One')
26
+ option(:two, 2, 'Two')
27
+ option(:three, 3, 'Three')
28
+ end
29
+
30
+ worker = worker_class.new(ImageOptim.new, :three => '...')
31
+
32
+ expect(worker.options).to eq(:one => 1, :two => 2, :three => '...')
33
+ end
34
+ end
35
+
36
+ describe '#optimize' do
37
+ it 'raises NotImplementedError' do
38
+ worker = Worker.new(ImageOptim.new, {})
39
+
40
+ expect do
41
+ worker.optimize(double, double)
42
+ end.to raise_error NotImplementedError
43
+ end
44
+ end
45
+
46
+ describe '#image_formats' do
47
+ {
48
+ 'GifOptim' => :gif,
49
+ 'JpegOptim' => :jpeg,
50
+ 'PngOptim' => :png,
51
+ 'SvgOptim' => :svg,
52
+ }.each do |class_name, image_format|
53
+ it "detects if class name contains #{image_format}" do
54
+ worker = stub_const(class_name, Class.new(Worker)).new(ImageOptim.new)
55
+ expect(worker.image_formats).to eq([image_format])
56
+ end
57
+ end
58
+
59
+ it 'fails if class name does not contain known type' do
60
+ worker = stub_const('TiffOptim', Class.new(Worker)).new(ImageOptim.new)
61
+ expect{ worker.image_formats }.to raise_error(/can't guess/)
62
+ end
63
+ end
64
+
65
+ describe '#inspect' do
66
+ it 'returns inspect String containing options' do
67
+ stub_const('DefOptim', Class.new(Worker) do
68
+ option(:one, 1, 'One')
69
+ option(:two, 2, 'Two')
70
+ option(:three, 3, 'Three')
71
+ end)
72
+
73
+ worker = DefOptim.new(ImageOptim.new, :three => '...')
74
+
75
+ expect(worker.inspect).to eq('#<DefOptim @one=1, @two=2, @three="...">')
76
+ end
77
+ end
78
+
79
+ describe '.inherited' do
80
+ it 'adds subclasses to klasses' do
81
+ base_class = Class.new{ extend ImageOptim::Worker::ClassMethods }
82
+ expect(base_class.klasses.to_a).to eq([])
83
+
84
+ worker_class = Class.new(base_class)
85
+ expect(base_class.klasses.to_a).to eq([worker_class])
86
+ end
87
+ end
88
+
89
+ describe '.create_all_by_format' do
90
+ it 'passes arguments to create_all' do
91
+ image_optim = double
92
+ options_proc = proc{ true }
93
+
94
+ expect(Worker).to receive(:create_all) do |arg, &block|
95
+ expect(arg).to eq(image_optim)
96
+ expect(block).to eq(options_proc)
97
+ []
98
+ end
99
+
100
+ Worker.create_all_by_format(image_optim, &options_proc)
101
+ end
102
+
103
+ it 'create hash by format' do
104
+ workers = [
105
+ double(:image_formats => [:a]),
106
+ double(:image_formats => [:a, :b]),
107
+ double(:image_formats => [:b, :c]),
108
+ ]
109
+
110
+ expect(Worker).to receive(:create_all).and_return(workers)
111
+
112
+ worker_by_format = {
113
+ :a => [workers[0], workers[1]],
114
+ :b => [workers[1], workers[2]],
115
+ :c => [workers[2]],
116
+ }
117
+
118
+ expect(Worker.create_all_by_format(double)).to eq(worker_by_format)
119
+ end
120
+ end
121
+
122
+ describe '.create_all' do
123
+ def worker_double(override = {})
124
+ stubs = {:resolve_used_bins! => nil, :run_order => 0}.merge(override)
125
+ instance_double(Worker, stubs)
126
+ end
127
+
128
+ def worker_class_doubles(workers)
129
+ workers.map{ |worker| class_double(Worker, :init => worker) }
130
+ end
131
+
132
+ let(:image_optim){ double(:allow_lossy => false, :timeout => 30) }
133
+
134
+ it 'creates all workers for which options_proc returns true' do
135
+ workers = Array.new(3){ worker_double }
136
+ klasses = worker_class_doubles(workers)
137
+ options_proc = proc do |klass|
138
+ klass == klasses[1] ? {:disable => true} : {}
139
+ end
140
+
141
+ allow(Worker).to receive(:klasses).and_return(klasses)
142
+
143
+ expect(Worker.create_all(image_optim, &options_proc)).
144
+ to eq([workers[0], workers[2]])
145
+ end
146
+
147
+ it 'handles workers initializing multiple instances' do
148
+ workers = [
149
+ worker_double,
150
+ [worker_double, worker_double, worker_double],
151
+ worker_double,
152
+ ]
153
+ klasses = worker_class_doubles(workers)
154
+
155
+ allow(Worker).to receive(:klasses).and_return(klasses)
156
+
157
+ expect(Worker.create_all(image_optim){ {} }).
158
+ to eq(workers.flatten)
159
+ end
160
+
161
+ describe 'with missing workers' do
162
+ let(:workers) do
163
+ %w[a b c c].map do |bin|
164
+ worker = worker_double
165
+ unless bin == 'b'
166
+ allow(worker).to receive(:resolve_used_bins!).
167
+ and_raise(BinResolver::BinNotFound, "not found #{bin}")
168
+ end
169
+ worker
170
+ end
171
+ end
172
+ let(:klasses){ worker_class_doubles(workers) }
173
+
174
+ before do
175
+ allow(Worker).to receive(:klasses).and_return(klasses)
176
+ end
177
+
178
+ describe 'if skip_missing_workers is true' do
179
+ it 'shows deduplicated warnings and returns resolved workers ' do
180
+ allow(image_optim).to receive(:skip_missing_workers).and_return(true)
181
+
182
+ expect(Worker).to receive(:warn).once.with('not found a')
183
+ expect(Worker).to receive(:warn).once.with('not found c')
184
+
185
+ expect(Worker.create_all(image_optim){ {} }).to eq([workers[1]])
186
+ end
187
+ end
188
+
189
+ describe 'if skip_missing_workers is false' do
190
+ it 'fails with a joint exception of deduplicated messages' do
191
+ allow(image_optim).to receive(:skip_missing_workers).and_return(false)
192
+
193
+ expect do
194
+ Worker.create_all(image_optim){ {} }
195
+ end.to raise_error(BinResolver::Error, [
196
+ 'Bin resolving errors:',
197
+ 'not found a',
198
+ 'not found c',
199
+ ].join("\n"))
200
+ end
201
+ end
202
+ end
203
+
204
+ it 'orders workers by run_order' do
205
+ run_orders = [10, -10, 0, 0, 0, 10, -10]
206
+ workers = run_orders.map do |run_order|
207
+ worker_double(:run_order => run_order)
208
+ end
209
+ klasses_list = worker_class_doubles(workers)
210
+
211
+ [
212
+ klasses_list,
213
+ klasses_list.reverse,
214
+ klasses_list.shuffle,
215
+ ].each do |klasses|
216
+ allow(Worker).to receive(:klasses).and_return(klasses)
217
+
218
+ expected_order = klasses.map(&:init).sort_by.with_index do |worker, i|
219
+ [worker.run_order, i]
220
+ end
221
+
222
+ expect(Worker.create_all(image_optim){ {} }).to eq(expected_order)
223
+ end
224
+ end
225
+
226
+ describe 'configuring workers' do
227
+ [:allow_lossy, :timeout].each do |option|
228
+ it "passes #{option} if worker has such attribute" do
229
+ klasses = worker_class_doubles([worker_double, worker_double])
230
+
231
+ allow(Worker).to receive(:klasses).and_return(klasses)
232
+
233
+ klasses[0].send(:attr_reader, option)
234
+ expect(klasses[0]).to receive(:init).
235
+ with(image_optim, hash_including(option))
236
+ expect(klasses[1]).to receive(:init).
237
+ with(image_optim, hash_not_including(option))
238
+
239
+ Worker.create_all(image_optim){ {} }
240
+ end
241
+ end
242
+
243
+ it 'allows overriding allow_lossy per worker' do
244
+ klasses = worker_class_doubles([worker_double, worker_double])
245
+ options_proc = proc do |klass|
246
+ klass == klasses[1] ? {:allow_lossy => :b} : {}
247
+ end
248
+
249
+ allow(Worker).to receive(:klasses).and_return(klasses)
250
+
251
+ klasses.each{ |klass| klass.send(:attr_reader, :allow_lossy) }
252
+ expect(klasses[0]).to receive(:init).
253
+ with(image_optim, hash_including(:allow_lossy => false))
254
+ expect(klasses[1]).to receive(:init).
255
+ with(image_optim, hash_including(:allow_lossy => :b))
256
+
257
+ Worker.create_all(image_optim, &options_proc)
258
+ end
259
+
260
+ it 'allows overriding timeout per worker' do
261
+ klasses = worker_class_doubles([worker_double, worker_double])
262
+ options_proc = proc do |klass|
263
+ klass == klasses[1] ? {:timeout => 50} : {}
264
+ end
265
+
266
+ allow(Worker).to receive(:klasses).and_return(klasses)
267
+
268
+ klasses.each{ |klass| klass.send(:attr_reader, :timeout) }
269
+ expect(klasses[0]).to receive(:init).
270
+ with(image_optim, hash_including(:timeout => 30))
271
+ expect(klasses[1]).to receive(:init).
272
+ with(image_optim, hash_including(:timeout => 50))
273
+
274
+ Worker.create_all(image_optim, &options_proc)
275
+ end
276
+ end
277
+ end
278
+
279
+ describe '.option' do
280
+ it 'runs option block in context of worker' do
281
+ stub_const('Abc', Class.new(Worker) do
282
+ option(:test, 1, 'Test context') do |_v|
283
+ some_instance_method
284
+ end
285
+ end)
286
+
287
+ expect_any_instance_of(Abc).
288
+ to receive(:some_instance_method).and_return(20)
289
+ expect(Abc.new(ImageOptim.new).test).to eq(20)
290
+ end
291
+
292
+ it 'returns instance of OptionDefinition' do
293
+ definition = nil
294
+ Class.new(Worker) do
295
+ definition = option(:test, 1, 'Test'){ |v| v }
296
+ end
297
+
298
+ expect(definition).to be_an(ImageOptim::OptionDefinition)
299
+ expect(definition.name).to eq(:test)
300
+ expect(definition.default).to eq(1)
301
+ end
302
+ end
303
+ end