diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1351a3..9c4f4c1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Ruby +name: Test on: push: @@ -14,14 +14,13 @@ jobs: strategy: matrix: ruby: - - '3.3' - + - 3.3 steps: - - uses: actions/checkout@v4 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - - run: bin/setup - - run: bin/rake test + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - run: bin/rake test:unit + - run: bin/rake test:e2e diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cb99575 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM ruby:3.3 + +ARG IMAGE_MAGICK_VERSION=7.1.1-33 + +# throw errors if Gemfile has been modified since Gemfile.lock +RUN bundle config --global frozen 1 + +RUN apt remove --purge -y "*imagemagick*" && \ + apt autoremove --purge -y +RUN apt-get update && apt-get install -y \ + checkinstall && \ + rm -rf /var/lib/apt/lists/* +RUN t=$(mktemp) && \ + wget 'https://dist.1-2.dev/imei.sh' -qO "$t" && \ + bash "$t" --checkinstall --imagemagick-version=$IMAGE_MAGICK_VERSION && \ + rm "$t" + +RUN apt-get update && apt-get install -y \ + inkscape \ + libvips \ + libvips-tools && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/src/app + +COPY lib/favicon_factory/version.rb ./lib/favicon_factory/version.rb +COPY favicon_factory.gemspec Gemfile Gemfile.lock . +RUN bundle install + +COPY . . + +CMD ["bin/console"] diff --git a/Gemfile.lock b/Gemfile.lock index 08c930a..bcb5b05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ PATH specs: favicon_factory (0.1.0) mini_magick (~> 4.12) + ruby-vips (~> 2.2) tty-option (~> 0.3.0) tty-which (~> 0.5.0) @@ -10,6 +11,7 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) + ffi (1.16.3) json (2.7.1) language_server-protocol (3.17.0.3) mini_magick (4.12.0) @@ -48,6 +50,8 @@ GEM rubocop-thread_safety (0.5.1) rubocop (>= 0.90.0) ruby-progressbar (1.13.0) + ruby-vips (2.2.1) + ffi (~> 1.12) strscan (3.1.0) tty-option (0.3.0) tty-which (0.5.0) diff --git a/README.md b/README.md index 2d41a3c..19f3932 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,20 @@ Icons will be generated in the same folder as the source SVG unless already exis ## Installation -ImageMagick and Inkscape are required: +Vips or ImageMagick+Inkscape are required. If both are present, FaviconFactory defaults to Vips. + +Vips: + +```bash +brew install vips +``` + +```bash +sudo apt-get install libvips +sudo apt-get install libvips-tools +``` + +ImageMagick and Inkscape: ```bash brew install imagemagick @@ -36,8 +49,8 @@ brew install inkscape ``` ```bash -sudo apt install imagemagick -sudo apt install inkscape +sudo apt-get install imagemagick # for v7 consider https://github.com/SoftCreatR/imei/ +sudo apt-get install inkscape ``` Add `favicon_factory` to the Gemfile: diff --git a/Rakefile b/Rakefile index 2bf771f..1d5cfa0 100644 --- a/Rakefile +++ b/Rakefile @@ -9,4 +9,35 @@ require "rubocop/rake_task" RuboCop::RakeTask.new -task default: %i[test rubocop] +task default: %i[test:unit test:e2e rubocop] + +namespace :test do + task :unit do + system("X=/e2e/ bin/rake test") + end + + task :e2e do + require "open3" + + statuses = [ + ["bin/rake test N=/e2e__with_deps/"], + ["apt-get remove -y --purge libvips libvips-tools && bin/rake test N=/e2e__with_deps/"], + ["apt-get remove -y --purge *imagemagick* inkscape libvips libvips-tools && bin/rake test N=/e2e__without_deps/"], + [ + "apt-get remove -y --purge libvips libvips-tools && bin/rake test N=/e2e__with_deps/", + "--build-arg IMAGE_MAGICK_VERSION=6.9.13-11" + ] + ].map do |command, build_args| + command = "docker run $(docker build -q #{build_args} .) bash -c '#{command}'" + stdout, stderr, status = Open3.capture3(command) + puts "=" * command.size + puts command + puts "=" * command.size + puts stderr + puts stdout + status + end + + raise "Some tests failed" if statuses.map(&:exitstatus).max.positive? + end +end diff --git a/bin/setup b/bin/setup index 5bdc68f..0d7deb8 100755 --- a/bin/setup +++ b/bin/setup @@ -17,9 +17,12 @@ FileUtils.chdir GEM_ROOT do when "darwin" system! "brew install imagemagick" system! "brew install inkscape" + system! "brew install vips" when "linux" - system! "sudo apt install imagemagick" - system! "sudo apt install inkscape" + system! "sudo apt-get install imagemagick" # for v7 consider https://github.com/SoftCreatR/imei/ + system! "sudo apt-get install inkscape" + system! "sudo apt-get install libvips" + system! "sudo apt-get install libvips-tools" else raise "Unsupported platform: #{Gem::Platform.local.os}" end diff --git a/favicon_factory.gemspec b/favicon_factory.gemspec index 4cf4c61..a321a94 100644 --- a/favicon_factory.gemspec +++ b/favicon_factory.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "mini_magick", "~> 4.12" + spec.add_dependency "ruby-vips", "~> 2.2" spec.add_dependency "tty-option", "~> 0.3.0" spec.add_dependency "tty-which", "~> 0.5.0" diff --git a/lib/favicon_factory.rb b/lib/favicon_factory.rb index 1614c66..541b74a 100644 --- a/lib/favicon_factory.rb +++ b/lib/favicon_factory.rb @@ -1,29 +1,19 @@ # frozen_string_literal: true require_relative "favicon_factory/version" -require "mini_magick" require "tty/which" require "tty/option" -module FaviconFactory - SVG_DENSITY = 1_000 +autoload(:MiniMagick, "mini_magick") +autoload(:Vips, "vips") +module FaviconFactory Params = Data.define(:favicon_svg, :background) do def dir File.dirname(favicon_svg) end end - PngParams = Data.define(:favicon_svg, :background, :size) do - def self.from_params(size, params) - new(**params.to_h, size: size) - end - - def dir - File.dirname(favicon_svg) - end - end - class Command include TTY::Option @@ -56,8 +46,8 @@ class Command option :background do short "-b" long "--background string" - default "white" - desc "Background color for apple-touch-icon.png" + default "#ffffff" + desc "Background hex color for apple-touch-icon.png" end flag :help do @@ -69,46 +59,33 @@ class Command class Cli def self.call - exit new(ARGV, $stderr).call + adapter = BaseAdapter.find + if adapter.nil? + stderr.puts "Error: Neither vips or imagemagick found, install one" + exit 1 + end + exit new(adapter: adapter, argv: ARGV, file: File, stderr: $stderr).call end - attr_reader :stderr - - def initialize(argv, stderr) + def initialize(adapter:, argv:, file:, stderr:) + @adapter = adapter @argv = argv + @file = file @stderr = stderr end def call - stderr.puts "imagemagick v7 not found, please install for best results" unless MiniMagick.imagemagick7? - stderr.puts "inkscape not found, install inkscape for best results" unless TTY::Which.which("inkscape") - - params, status = parse(@argv) + params, status = parse(argv) return status if status >= 0 - [ - Thread.new { create("favicon.ico", params) }, - Thread.new { create("icon-192.png", PngParams.from_params(192, params)) }, - Thread.new { create("icon-512.png", PngParams.from_params(512, params)) }, - Thread.new { create("apple-touch-icon.png", params) }, - Thread.new { create("manifest.webmanifest", params) } - ] - .each(&:join) - - stderr.puts <<~TEXT - Info: Add the following to the `` - - - - - - TEXT - + adapter.new(file: file, params: params, stderr: stderr).create_icons 0 end private + attr_reader :stderr, :file, :adapter, :argv + def parse(argv) command = Command.new.parse(argv) params = command.params @@ -118,19 +95,67 @@ def parse(argv) params = params.to_h favicon_svg = params.fetch(:favicon_svg) return exit_message(1, "Error: #{favicon_svg} does not end with .svg") unless favicon_svg.end_with?(".svg") - return exit_message(1, "Error: #{favicon_svg} does not exist") unless File.exist?(favicon_svg) + return exit_message(1, "Error: #{favicon_svg} does not exist") unless file.exist?(favicon_svg) - [Params.new(favicon_svg: favicon_svg, background: params.fetch(:background)), -1] + background = params.fetch(:background) + unless hex?(background) + return exit_message(1, "Error: #{background} is not a valid color, use a hex value like #0099ff") + end + + [Params.new(favicon_svg: favicon_svg, background: background), -1] + end + + def hex?(string) + string = string.delete_prefix("#") + string.split("").all? { |char| char.match?(/^[0-9a-fA-F]$/) } end def exit_message(status, message) stderr.puts message [nil, status] end + end + + class BaseAdapter + class << self + def find + if TTY::Which.which("vips") || TTY::Which.which("libvips") + VipsAdapter + elsif TTY::Which.which("magick") || TTY::Which.which("convert") + ImageMagickAdapter + end + end + end + + def initialize(file:, params:, stderr:) + @file = file + @params = params + @stderr = stderr + end + + def create_icons + create_by_name + .keys + .map { |name| Thread.new { create(name, params) } } + .each(&:join) + + stderr.puts <<~TEXT + Info: Add the following to the `` + + + + + + TEXT + end + + private + + attr_reader :params, :stderr, :file def create(name, params) - path = File.join(params.dir, name) - if File.exist?(path) + path = file.join(params.dir, name) + if file.exist?(path) stderr.puts "Info: Skipping #{path} because it already exists" return end @@ -142,13 +167,76 @@ def create(name, params) def create_by_name { "favicon.ico" => method(:ico!), - "icon-192.png" => method(:png!), - "icon-512.png" => method(:png!), + "icon-192.png" => method(:png_192!), + "icon-512.png" => method(:png_512!), "apple-touch-icon.png" => method(:touch!), "manifest.webmanifest" => method(:manifest!) } end + def png_192!(path, params) + png!(path, params, 192) + end + + def png_512!(path, params) + png!(path, params, 512) + end + + def manifest!(path, _params) + file.write(path, <<~MANIFEST) + { + "icons": [ + { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, + { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" } + ] + } + MANIFEST + end + end + + class VipsAdapter < BaseAdapter + def ico!(path, params) + png = Vips::Image.thumbnail(params.favicon_svg, 32).write_to_buffer(".png") + # https://www.meziantou.net/creating-ico-files-from-multiple-images-in-dotnet.htm + ico = [0, 1, 1].pack("S<*") + [32, 32, 0, 0].pack("C*") + [1, 32].pack("S<*") + [png.size, 22].pack("L<*") + png + file.write(path, ico) + end + + def png!(path, params, size) + Vips::Image.thumbnail(params.favicon_svg, size).write_to_file(path) + end + + def touch!(path, params) + svg = Vips::Image.thumbnail(params.favicon_svg, 160).gravity("centre", 180, 180) + image = square(180, params.background).composite(svg, :over) + image.write_to_file(path) + end + + private + + def square(size, hex) + pixel = (Vips::Image.black(1, 1) + hex2rgb(hex)).cast(:uchar) + pixel.embed 0, 0, size, size, extend: :copy + end + + def hex2rgb(hex) + hex = hex.delete_prefix("#") + r = hex[0..1].to_i(16) + g = hex[2..3].to_i(16) + b = hex[4..5].to_i(16) + [r, g, b] + end + end + + class ImageMagickAdapter < BaseAdapter + SVG_DENSITY = 1_000 + + def initialize(**) + super + stderr.puts "Warn: Install imagemagick v7 for best results, using v6" unless MiniMagick.imagemagick7? + stderr.puts "Warn: Inkscape not found, install it for best results" unless TTY::Which.which("inkscape") + end + def ico!(path, params) MiniMagick::Tool::Convert.new do |convert| convert.density(SVG_DENSITY).background("none") @@ -158,11 +246,11 @@ def ico!(path, params) end end - def png!(path, params) + def png!(path, params, size) MiniMagick::Tool::Convert.new do |convert| convert.density(SVG_DENSITY).background("none") convert << params.favicon_svg - convert.resize("#{params.size}x#{params.size}") + convert.resize("#{size}x#{size}") convert << path end end @@ -175,16 +263,5 @@ def touch!(path, params) convert << path end end - - def manifest!(path, _params) - File.write(path, <<~MANIFEST) - { - "icons": [ - { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, - { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" } - ] - } - MANIFEST - end end end diff --git a/samples/github.com/apple-touch-icon.png b/samples/github.com/image_magick/apple-touch-icon.png similarity index 97% rename from samples/github.com/apple-touch-icon.png rename to samples/github.com/image_magick/apple-touch-icon.png index d19092e..ba03faa 100644 Binary files a/samples/github.com/apple-touch-icon.png and b/samples/github.com/image_magick/apple-touch-icon.png differ diff --git a/samples/github.com/favicon.ico b/samples/github.com/image_magick/favicon.ico similarity index 100% rename from samples/github.com/favicon.ico rename to samples/github.com/image_magick/favicon.ico diff --git a/samples/github.com/favicon.svg b/samples/github.com/image_magick/favicon.svg similarity index 89% rename from samples/github.com/favicon.svg rename to samples/github.com/image_magick/favicon.svg index bf1081a..3913791 100644 --- a/samples/github.com/favicon.svg +++ b/samples/github.com/image_magick/favicon.svg @@ -1,3 +1,3 @@ - + diff --git a/samples/github.com/icon-192.png b/samples/github.com/image_magick/icon-192.png similarity index 95% rename from samples/github.com/icon-192.png rename to samples/github.com/image_magick/icon-192.png index e049e71..cad27c2 100644 Binary files a/samples/github.com/icon-192.png and b/samples/github.com/image_magick/icon-192.png differ diff --git a/samples/github.com/icon-512.png b/samples/github.com/image_magick/icon-512.png similarity index 99% rename from samples/github.com/icon-512.png rename to samples/github.com/image_magick/icon-512.png index f1e61ad..3ee61ee 100644 Binary files a/samples/github.com/icon-512.png and b/samples/github.com/image_magick/icon-512.png differ diff --git a/samples/github.com/manifest.webmanifest b/samples/github.com/image_magick/manifest.webmanifest similarity index 100% rename from samples/github.com/manifest.webmanifest rename to samples/github.com/image_magick/manifest.webmanifest diff --git a/samples/github.com/vips/apple-touch-icon.png b/samples/github.com/vips/apple-touch-icon.png new file mode 100644 index 0000000..e721f89 Binary files /dev/null and b/samples/github.com/vips/apple-touch-icon.png differ diff --git a/samples/github.com/vips/favicon.ico b/samples/github.com/vips/favicon.ico new file mode 100644 index 0000000..b9c3cd5 Binary files /dev/null and b/samples/github.com/vips/favicon.ico differ diff --git a/samples/github.com/vips/favicon.svg b/samples/github.com/vips/favicon.svg new file mode 100644 index 0000000..3913791 --- /dev/null +++ b/samples/github.com/vips/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/samples/github.com/vips/icon-192.png b/samples/github.com/vips/icon-192.png new file mode 100644 index 0000000..7e545b7 Binary files /dev/null and b/samples/github.com/vips/icon-192.png differ diff --git a/samples/github.com/vips/icon-512.png b/samples/github.com/vips/icon-512.png new file mode 100644 index 0000000..433dcdc Binary files /dev/null and b/samples/github.com/vips/icon-512.png differ diff --git a/samples/rictionary/manifest.webmanifest b/samples/github.com/vips/manifest.webmanifest similarity index 100% rename from samples/rictionary/manifest.webmanifest rename to samples/github.com/vips/manifest.webmanifest diff --git a/samples/rictionary/apple-touch-icon.png b/samples/rictionary/image_magick/apple-touch-icon.png similarity index 96% rename from samples/rictionary/apple-touch-icon.png rename to samples/rictionary/image_magick/apple-touch-icon.png index 51bf5d0..cec2f96 100644 Binary files a/samples/rictionary/apple-touch-icon.png and b/samples/rictionary/image_magick/apple-touch-icon.png differ diff --git a/samples/rictionary/favicon.ico b/samples/rictionary/image_magick/favicon.ico similarity index 100% rename from samples/rictionary/favicon.ico rename to samples/rictionary/image_magick/favicon.ico diff --git a/samples/rictionary/favicon.svg b/samples/rictionary/image_magick/favicon.svg similarity index 100% rename from samples/rictionary/favicon.svg rename to samples/rictionary/image_magick/favicon.svg diff --git a/samples/rictionary/icon-192.png b/samples/rictionary/image_magick/icon-192.png similarity index 97% rename from samples/rictionary/icon-192.png rename to samples/rictionary/image_magick/icon-192.png index fcd7f4c..bbf2177 100644 Binary files a/samples/rictionary/icon-192.png and b/samples/rictionary/image_magick/icon-192.png differ diff --git a/samples/rictionary/icon-512.png b/samples/rictionary/image_magick/icon-512.png similarity index 98% rename from samples/rictionary/icon-512.png rename to samples/rictionary/image_magick/icon-512.png index 3deae46..f3e9461 100644 Binary files a/samples/rictionary/icon-512.png and b/samples/rictionary/image_magick/icon-512.png differ diff --git a/samples/rictionary/image_magick/manifest.webmanifest b/samples/rictionary/image_magick/manifest.webmanifest new file mode 100644 index 0000000..a893e5b --- /dev/null +++ b/samples/rictionary/image_magick/manifest.webmanifest @@ -0,0 +1,6 @@ +{ + "icons": [ + { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, + { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" } + ] +} diff --git a/samples/rictionary/vips/apple-touch-icon.png b/samples/rictionary/vips/apple-touch-icon.png new file mode 100644 index 0000000..3974ac1 Binary files /dev/null and b/samples/rictionary/vips/apple-touch-icon.png differ diff --git a/samples/rictionary/vips/favicon.ico b/samples/rictionary/vips/favicon.ico new file mode 100644 index 0000000..695deeb Binary files /dev/null and b/samples/rictionary/vips/favicon.ico differ diff --git a/samples/rictionary/vips/favicon.svg b/samples/rictionary/vips/favicon.svg new file mode 100644 index 0000000..bc5b98c --- /dev/null +++ b/samples/rictionary/vips/favicon.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/rictionary/vips/icon-192.png b/samples/rictionary/vips/icon-192.png new file mode 100644 index 0000000..b09a4ed Binary files /dev/null and b/samples/rictionary/vips/icon-192.png differ diff --git a/samples/rictionary/vips/icon-512.png b/samples/rictionary/vips/icon-512.png new file mode 100644 index 0000000..6011025 Binary files /dev/null and b/samples/rictionary/vips/icon-512.png differ diff --git a/samples/rictionary/vips/manifest.webmanifest b/samples/rictionary/vips/manifest.webmanifest new file mode 100644 index 0000000..a893e5b --- /dev/null +++ b/samples/rictionary/vips/manifest.webmanifest @@ -0,0 +1,6 @@ +{ + "icons": [ + { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, + { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" } + ] +} diff --git a/test/test_e2e.rb b/test/test_e2e.rb new file mode 100644 index 0000000..73dc3e4 --- /dev/null +++ b/test/test_e2e.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "test_helper" +require "open3" + +class TestE2e < Minitest::Test + def test_e2e__without_deps__with_existing_svg__it_succeeds + with_svg do |_dir, path| + _, stderr, status = Open3.capture3("bundle exec exe/favicon_factory #{path}") + + assert_equal 1, status.exitstatus + assert_match Regexp.new("Error: Neither vips or imagemagick found, install one"), stderr + end + end + + def test_e2e__with_deps__with_existing_svg__it_succeeds + with_svg do |dir, path| + _, stderr, status = Open3.capture3("bundle exec exe/favicon_factory #{path}") + + assert_equal 0, status.exitstatus + assert_match Regexp.new("Info: Add the following to the ``"), stderr + TARGETS.each do |name| + path = File.join(dir, name) + + assert_match Regexp.new("Info: Generating #{path}"), stderr + assert_path_exists path + end + end + end +end diff --git a/test/test_favicon_factory.rb b/test/test_favicon_factory.rb index a400cc4..e4e4aab 100644 --- a/test/test_favicon_factory.rb +++ b/test/test_favicon_factory.rb @@ -2,60 +2,30 @@ require "test_helper" require "stringio" -require "open3" class TestFaviconFactory < Minitest::Test - TARGETS = [ - "favicon.ico", - "icon-192.png", - "icon-512.png", - "apple-touch-icon.png", - "manifest.webmanifest" - ].freeze - - def setup - FaviconFactory.module_eval do - remove_const(:SVG_DENSITY) - const_set(:SVG_DENSITY, 1) # make tests faster - end - end - - def test_e2e__with_existing_svg__it_succeeds - with_svg do |dir, path| - _, stderr, status = Open3.capture3("bundle exec exe/favicon_factory #{path}") - - assert_equal 0, status.exitstatus - assert_match Regexp.new("Info: Add the following to the ``"), stderr - TARGETS.each do |name| - path = File.join(dir, name) - assert_match Regexp.new("Info: Generating #{path}"), stderr - assert_path_exists path - end - end - end - - def test__with_no_args__it_exits_1_and_prints_usage - args = [] + def test__with_no_args__it_errors_and_prints_usage + argv = [] stderr = StringIO.new - status = FaviconFactory::Cli.new(args, stderr).call + status = FaviconFactory::Cli.new(argv: argv, stderr: stderr, file: File, adapter: nil).call assert_equal 1, status assert_match Regexp.new("Error: argument 'favicon_svg' must be provided"), stderr.string end def test__with_arg_not_being_an_svg__it_errors - args = ["one"] + argv = ["one"] stderr = StringIO.new - status = FaviconFactory::Cli.new(args, stderr).call + status = FaviconFactory::Cli.new(argv: argv, stderr: stderr, file: File, adapter: nil).call assert_equal 1, status assert_match Regexp.new("Error: one does not end with .svg"), stderr.string end def test__with_non_existing_svg__it_errors - args = ["one.svg"] + argv = ["one.svg"] stderr = StringIO.new - status = FaviconFactory::Cli.new(args, stderr).call + status = FaviconFactory::Cli.new(argv: argv, stderr: stderr, file: File, adapter: nil).call assert_equal 1, status assert_match Regexp.new("Error: one.svg does not exist"), stderr.string @@ -63,21 +33,32 @@ def test__with_non_existing_svg__it_errors def test__it_prints_help ["--help", "-h"].each do |flag| - args = [flag] + argv = [flag] stderr = StringIO.new - status = FaviconFactory::Cli.new(args, stderr).call + status = FaviconFactory::Cli.new(argv: argv, stderr: stderr, file: File, adapter: nil).call assert_equal 0, status assert_match Regexp.new("Usage:"), stderr.string end end - def test__with_existing_files__it_skips + def test__with_invalid_background__it_errors + with_svg do |_dir, path| + argv = ["--background", "blue", path] + stderr = StringIO.new + status = FaviconFactory::Cli.new(argv: argv, stderr: stderr, file: File, adapter: nil).call + + assert_equal 1, status + assert_match Regexp.new("Error: blue is not a valid color, use a hex value like #0099ff"), stderr.string + end + end + + def test__vips__with_existing_files__it_skips with_svg do |dir, path| TARGETS.each { FileUtils.touch(File.join(dir, _1)) } - args = [path] + argv = [path] stderr = StringIO.new - status = FaviconFactory::Cli.new(args, stderr).call + status = FaviconFactory::Cli.new(argv: argv, stderr: stderr, file: File, adapter: TestAdapter).call assert_equal 0, status TARGETS.each do |name| @@ -88,42 +69,44 @@ def test__with_existing_files__it_skips end end - def test__it_uses_the_background_option - testable_call = Class.new(FaviconFactory::Cli) do - define_method(:touch!) do |_, params| - raise(params.background) if params.background != "blue" + def test__vips__it_uses_the_background_option + called = 0 + + TestAdapter.class_eval do + alias_method :touch_!, :touch! + + define_method(:touch!) do |*| + called += 1 + raise(params.background) if params.background != "#0099ff" end end with_svg do |_dir, path| - status = testable_call.new([path, "--background", "blue"], StringIO.new).call + argv = ["--background", "#0099ff", path] + status = FaviconFactory::Cli.new(argv: argv, stderr: StringIO.new, file: File, adapter: TestAdapter).call assert_equal 0, status end with_svg do |_dir, path| - status = testable_call.new([path, "--background=blue"], StringIO.new).call + argv = [path, "--background=#0099ff"] + status = FaviconFactory::Cli.new(argv: argv, stderr: StringIO.new, file: File, adapter: TestAdapter).call assert_equal 0, status end with_svg do |_dir, path| - status = testable_call.new([path, "-b", "blue"], StringIO.new).call + argv = [path, "-b", "#0099ff"] + status = FaviconFactory::Cli.new(argv: argv, stderr: StringIO.new, file: File, adapter: TestAdapter).call assert_equal 0, status end - end - - private - def with_svg - Tempfile.create(["one", ".svg"]) do |file| - file.write("") - file.rewind - path = file.path - dir = File.dirname(path) - TARGETS.each { FileUtils.rm_f(File.join(dir, _1)) } - yield(dir, path) + assert_equal 3, called + ensure + TestAdapter.class_eval do + remove_method(:touch!) + alias_method :touch!, :touch_! end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 319eb6e..7cbc680 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,3 +4,34 @@ require "favicon_factory" require "minitest/autorun" + +TARGETS = [ + "favicon.ico", + "icon-192.png", + "icon-512.png", + "apple-touch-icon.png", + "manifest.webmanifest" +].freeze + +class TestAdapter < FaviconFactory::BaseAdapter + def ico!(path, params); end + + def png!(path, params, size); end + + def touch!(path, params); end +end + +def with_svg + Tempfile.create(["one", ".svg"]) do |file| + file.write(<<~SVG) + + + + SVG + file.rewind + path = file.path + dir = File.dirname(path) + TARGETS.each { FileUtils.rm_f(File.join(dir, _1)) } + yield(dir, path) + end +end