nameplate 0.1.0

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 (39) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +122 -0
  4. data/Rakefile +39 -0
  5. data/lib/nameplate/avatar/cache.rb +59 -0
  6. data/lib/nameplate/avatar/generator.rb +237 -0
  7. data/lib/nameplate/avatar/identity.rb +41 -0
  8. data/lib/nameplate/avatar.rb +37 -0
  9. data/lib/nameplate/colors/palette.rb +45 -0
  10. data/lib/nameplate/colors/palettes/custom.rb +20 -0
  11. data/lib/nameplate/colors/palettes/dracula.rb +29 -0
  12. data/lib/nameplate/colors/palettes/google.rb +55 -0
  13. data/lib/nameplate/colors/palettes/iwanthue.rb +234 -0
  14. data/lib/nameplate/colors/palettes/jedi_light.rb +26 -0
  15. data/lib/nameplate/colors/palettes/monokai.rb +27 -0
  16. data/lib/nameplate/colors/palettes/pastel.rb +24 -0
  17. data/lib/nameplate/colors.rb +42 -0
  18. data/lib/nameplate/configuration.rb +109 -0
  19. data/lib/nameplate/fonts/Lato-Light.ttf +0 -0
  20. data/lib/nameplate/fonts/Roboto-Medium +0 -0
  21. data/lib/nameplate/has_avatar.rb +33 -0
  22. data/lib/nameplate/image/resize.rb +111 -0
  23. data/lib/nameplate/results/failure_result.rb +35 -0
  24. data/lib/nameplate/results/success_result.rb +27 -0
  25. data/lib/nameplate/utils/path_helper.rb +18 -0
  26. data/lib/nameplate/version.rb +5 -0
  27. data/lib/nameplate/view_helpers/avatar_helper.rb +60 -0
  28. data/lib/nameplate.rb +57 -0
  29. data/spec/fixtures/in.png +0 -0
  30. data/spec/nameplate/avatar/cache_spec.rb +35 -0
  31. data/spec/nameplate/avatar/generator_spec.rb +108 -0
  32. data/spec/nameplate/avatar/identity_spec.rb +27 -0
  33. data/spec/nameplate/colors_spec.rb +34 -0
  34. data/spec/nameplate/image/resize_spec.rb +96 -0
  35. data/spec/nameplate/utils/path_helper_spec.rb +16 -0
  36. data/spec/nameplate/view_helpers/avatar_helper_spec.rb +38 -0
  37. data/spec/nameplate_spec.rb +102 -0
  38. data/spec/spec_helper.rb +13 -0
  39. metadata +111 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NamePlate
4
+ module Results
5
+ # Represents a successful operation result.
6
+ #
7
+ # Provides a consistent API for checking success
8
+ # and accessing the returned value.
9
+ class SuccessResult
10
+ attr_reader :value
11
+
12
+ def initialize(value:)
13
+ @value = value
14
+ end
15
+
16
+ # @return [Boolean]
17
+ def success?
18
+ true
19
+ end
20
+
21
+ # @return [Boolean]
22
+ def failure?
23
+ false
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Utils
4
+ module PathHelper
5
+ module_function
6
+
7
+ # Convert a public file-system path to a URL path.
8
+ #
9
+ # @param path [String, Pathname]
10
+ # @return [String]
11
+ #
12
+ # Examples:
13
+ # PathHelper.path_to_url("public/avatars/a.png") #=> "/avatars/a.png"
14
+ def path_to_url(path)
15
+ path.to_s.sub(%r{\Apublic/}, "/")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NamePlate
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view"
4
+ require "action_view/helpers"
5
+
6
+ module NamePlate
7
+ module ViewHelpers
8
+ # Rails view helpers for rendering avatars in controllers/views.
9
+ #
10
+ # Usage in Rails:
11
+ # include NamePlate::ViewHelpers::Avatar
12
+ #
13
+ # Then in your views:
14
+ # nameplate_for("Tony", 200)
15
+ # nameplate_url("Tony", 200)
16
+ # nameplate_tag("Tony", 200, class: "avatar")
17
+ module AvatarHelper
18
+ if defined?(ActionView::Helpers::AssetTagHelper)
19
+ include ActionView::Helpers::AssetTagHelper
20
+ end
21
+
22
+ # Return path to generated avatar image
23
+ #
24
+ # @param name [String] the name to base avatar on
25
+ # @param size [Integer] requested size in px
26
+ # @return [String] filesystem path
27
+ def nameplate_for(name, size = 64)
28
+ NamePlate::Avatar.generate(name, size)
29
+ end
30
+
31
+ # Return URL for generated avatar image
32
+ #
33
+ # @param name [String] the name to base avatar on
34
+ # @param size [Integer] requested size in px
35
+ # @return [String] URL path
36
+ def nameplate_url(name, size = 64)
37
+ NamePlate.path_to_url(nameplate_for(name, size))
38
+ end
39
+
40
+ # Render an <img> tag for the avatar
41
+ #
42
+ # @param name [String] the name to base avatar on
43
+ # @param size [Integer] requested size
44
+ # @param options [Hash] HTML options (e.g., :class)
45
+ # @return [String] HTML img tag
46
+ def nameplate_tag(name, size = 64, options = {})
47
+ src = nameplate_url(name, size)
48
+
49
+ if defined?(ActionView::Helpers::AssetTagHelper)
50
+ extend ActionView::Helpers::AssetTagHelper
51
+ image_tag(src, options.merge(alt: name))
52
+ else
53
+ class_attr = options.fetch(:class, nil)
54
+ class_str = class_attr ? %( class="#{class_attr}") : ""
55
+ %(<img alt="#{name}"#{class_str} src="#{src}" />)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
data/lib/nameplate.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # stdlib dependencies used internally
4
+ require "open3"
5
+ require_relative "nameplate/version"
6
+ require_relative "nameplate/configuration"
7
+ require_relative "nameplate/results/success_result"
8
+ require_relative "nameplate/results/failure_result"
9
+ require_relative "nameplate/avatar"
10
+ require_relative "nameplate/colors"
11
+ require_relative "nameplate/has_avatar"
12
+ require_relative "nameplate/image/resize"
13
+ require_relative "nameplate/utils/path_helper"
14
+ require_relative "nameplate/view_helpers/avatar_helper"
15
+
16
+ module NamePlate
17
+ extend NamePlate::Configuration
18
+
19
+ # Setup DSL for configuration
20
+ #
21
+ # Example:
22
+ # NamePlate.setup do |config|
23
+ # config.cache_base_path = "public/system"
24
+ # config.colors_palette = :dracula
25
+ # end
26
+ def self.setup
27
+ yield(self)
28
+ end
29
+
30
+ # Public API: generate avatar for a given username
31
+ #
32
+ # @param username [String]
33
+ # @param size [Integer]
34
+ # @return [String] path to generated avatar
35
+ def self.generate(username, size)
36
+ Avatar.generate(username, size)
37
+ end
38
+
39
+ # Resize an image and return a structured result
40
+ #
41
+ # @param from [String] source path
42
+ # @param to [String] destination path
43
+ # @param width [Integer]
44
+ # @param height [Integer]
45
+ # @return [SuccessResult, FailureResult]
46
+ def self.resize_image(from:, to:, width:, height:)
47
+ Image::Resize.call(from:, to:, width:, height:)
48
+ end
49
+
50
+ # Convert a filesystem path to a URL
51
+ #
52
+ # @param path [String, Pathname]
53
+ # @return [String]
54
+ def self.path_to_url(path)
55
+ Utils::PathHelper.path_to_url(path)
56
+ end
57
+ end
Binary file
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NamePlate::Avatar::Cache do
6
+ let(:tmpdir) { File.expand_path("../../tmp/cache", __dir__) }
7
+
8
+ before do
9
+ FileUtils.rm_rf(tmpdir)
10
+ FileUtils.mkdir_p(tmpdir)
11
+ @orig = NamePlate.cache_base_path
12
+ NamePlate.cache_base_path = tmpdir
13
+ end
14
+
15
+ after do
16
+ NamePlate.cache_base_path = @orig
17
+ FileUtils.rm_rf(tmpdir)
18
+ end
19
+
20
+ let(:identity) { NamePlate::Avatar::Identity.new([226, 95, 81], "T") }
21
+
22
+ describe ".base_path" do
23
+ it "builds base path with version" do
24
+ expect(described_class.base_path).to eq(File.join(tmpdir, "nameplate", NamePlate::Avatar::VERSION.to_s))
25
+ end
26
+ end
27
+
28
+ describe ".path" do
29
+ it "creates directories and returns full file path" do
30
+ path = described_class.path(identity, 64)
31
+ expect(path).to end_with("/T/226_95_81/64.png")
32
+ expect(File).to exist(File.dirname(path))
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NamePlate::Avatar::Generator do
6
+ let(:identity) { NamePlate::Avatar::Identity.new([226, 95, 81], "T") }
7
+ let(:target_path) { File.expand_path("../../tmp/generated/64.png", __dir__) }
8
+ let(:fake_font) { "/fake/font.ttf" }
9
+ let(:mm_cmd) do
10
+ double("MiniMagick::Command",
11
+ :size => nil, :<< => nil, :pointsize => nil, :font => nil,
12
+ :weight => nil, :fill => nil, :gravity => nil, :annotate => nil)
13
+ end
14
+
15
+ before do
16
+ # Never touch the filesystem
17
+ allow(FileUtils).to receive(:mkdir_p)
18
+ allow(FileUtils).to receive(:rm_f)
19
+
20
+ # Stub MiniMagick constant if not loaded
21
+ stub_const("MiniMagick", Module.new) unless defined?(MiniMagick)
22
+
23
+ # Avoid any real font lookups
24
+ allow(NamePlate).to receive(:font).and_return(fake_font)
25
+ allow(File).to receive(:exist?).and_call_original
26
+ allow(File).to receive(:exist?).with(fake_font).and_return(true)
27
+
28
+ # Deterministic identity + cache path
29
+ allow(NamePlate::Avatar::Identity).to receive(:from_username).and_return(identity)
30
+ allow(NamePlate::Avatar::Cache).to receive(:path).and_return(target_path)
31
+ end
32
+
33
+ describe ".call" do
34
+ it "returns cached path when file exists and cache=true" do
35
+ allow(File).to receive(:exist?).with(target_path).and_return(true)
36
+
37
+ expect(described_class.call("Tony", 64, cache: true)).to eq(target_path)
38
+ end
39
+
40
+ it "generates via MiniMagick when cache miss" do
41
+ allow(File).to receive(:exist?).with(target_path).and_return(false)
42
+ expect(MiniMagick).to receive(:convert).and_yield(mm_cmd)
43
+
44
+ expect(described_class.call("Tony", 64, cache: false)).to eq(target_path)
45
+ end
46
+
47
+ it "caps size at Avatar::FULLSIZE" do
48
+ allow(File).to receive(:exist?).with(target_path).and_return(false)
49
+ allow(MiniMagick).to receive(:convert).and_yield(mm_cmd)
50
+
51
+ expect(NamePlate::Avatar::Cache).to receive(:path) do |_, s|
52
+ expect(s).to eq(NamePlate::Avatar::FULLSIZE)
53
+ target_path
54
+ end
55
+
56
+ described_class.call("Tony", NamePlate::Avatar::FULLSIZE + 100, cache: false)
57
+ end
58
+ end
59
+
60
+ describe ".async_call" do
61
+ before do
62
+ # Always simulate cache miss
63
+ allow(File).to receive(:exist?).with(target_path).and_return(false)
64
+ end
65
+
66
+ let(:future) { described_class.async_call("Tony", 64) }
67
+
68
+ def stub_minimagick_success
69
+ allow(MiniMagick).to receive(:convert).and_yield(mm_cmd)
70
+ end
71
+
72
+ def stub_minimagick_failure(msg = "boom")
73
+ allow(MiniMagick).to receive(:convert).and_raise(msg)
74
+ end
75
+
76
+ it "returns a future that resolves to the avatar path" do
77
+ stub_minimagick_success
78
+ expect(future.value!).to eq(target_path)
79
+ end
80
+
81
+ it "wraps MiniMagick errors in ImageMagickError" do
82
+ stub_minimagick_failure
83
+ expect { future.value! }.to raise_error(described_class::ImageMagickError, /MiniMagick failed/)
84
+ end
85
+
86
+ it "wraps filesystem errors in FileSystemError" do
87
+ allow(NamePlate::Avatar::Cache).to receive(:path).and_raise(Errno::EACCES)
88
+ expect { future.value! }.to raise_error(described_class::FileSystemError, /Permission denied/)
89
+ end
90
+
91
+ it "wraps unexpected errors in GenerationError" do
92
+ allow(NamePlate::Avatar::Identity).to receive(:from_username).and_raise("weird failure")
93
+ expect { future.value! }.to raise_error(described_class::GenerationError, /Unexpected error/)
94
+ end
95
+ end
96
+
97
+ describe "input validation" do
98
+ [
99
+ {input: [" ", 64], error: described_class::ConfigurationError, description: "blank username"},
100
+ {input: ["Tony", 0], error: described_class::ConfigurationError, description: "non-positive size"},
101
+ {input: ["Tony", -5], error: described_class::ConfigurationError, description: "negative size"}
102
+ ].each do |test_case|
103
+ it "raises #{test_case[:error]} for #{test_case[:description]}" do
104
+ expect { described_class.call(*test_case[:input]) }.to raise_error(test_case[:error])
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NamePlate::Avatar::Identity do
6
+ describe ".from_username" do
7
+ before do
8
+ allow(NamePlate::Colors).to receive(:for).and_return([1, 2, 3])
9
+ end
10
+
11
+ it "uses first letter for single-word usernames" do
12
+ identity = described_class.from_username("tony")
13
+ expect(identity.letters).to eq("T")
14
+ expect(identity.color).to eq([1, 2, 3])
15
+ end
16
+
17
+ it "uses two letters for multi-word usernames" do
18
+ identity = described_class.from_username("Tony Lombardi")
19
+ expect(identity.letters).to eq("TL")
20
+ end
21
+
22
+ it "upcases letters and trims whitespace" do
23
+ identity = described_class.from_username(" ada ")
24
+ expect(identity.letters).to eq("A")
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NamePlate::Colors do
6
+ describe ".valid_custom_palette?" do
7
+ it "returns true for a valid palette of hex colors (2..20)" do
8
+ palette = ["#fff", "#a1b2c3", "#123456"]
9
+ expect(described_class.valid_custom_palette?(palette)).to be(true)
10
+ end
11
+
12
+ it "returns false for nil" do
13
+ expect(described_class.valid_custom_palette?(nil)).to be(false)
14
+ end
15
+
16
+ it "returns false for non-array" do
17
+ expect(described_class.valid_custom_palette?("#fff")).to be(false)
18
+ end
19
+
20
+ it "returns false for invalid hex strings" do
21
+ expect(described_class.valid_custom_palette?(["fff", "#12"])).to be(false)
22
+ end
23
+
24
+ it "returns false when too few colors" do
25
+ expect(described_class.valid_custom_palette?(["#fff"]))
26
+ .to be(false)
27
+ end
28
+
29
+ it "returns false when too many colors" do
30
+ palette = Array.new(21, "#fff")
31
+ expect(described_class.valid_custom_palette?(palette)).to be(false)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NamePlate::Image::Resize do
6
+ let(:src) { File.expand_path("../../fixtures/src.png", __dir__) }
7
+ let(:dst) { File.expand_path("../../tmp/out.png", __dir__) }
8
+ let(:width) { 200 }
9
+ let(:height) { 200 }
10
+
11
+ before do
12
+ FileUtils.mkdir_p(File.dirname(src))
13
+ File.write(src, "PNG") unless File.exist?(src)
14
+
15
+ FileUtils.mkdir_p(File.dirname(dst))
16
+ end
17
+
18
+ after do
19
+ FileUtils.rm_f(src)
20
+ FileUtils.rm_f(dst)
21
+ end
22
+
23
+ describe ".call" do
24
+ context "when source does not exist" do
25
+ it "returns FailureResult" do
26
+ result = described_class.call(from: "missing.png", to: dst, width: 10, height: 10)
27
+
28
+ expect(result).to be_a(NamePlate::Results::FailureResult)
29
+ end
30
+ end
31
+
32
+ context "when width is not positive" do
33
+ it "returns FailureResult" do
34
+ result = described_class.call(from: src, to: dst, width: 0, height: 10)
35
+
36
+ expect(result.error[:message]).to include("Width must be positive integer")
37
+ end
38
+ end
39
+
40
+ context "when height is not positive" do
41
+ it "returns FailureResult" do
42
+ result = described_class.call(from: src, to: dst, width: 10, height: 0)
43
+
44
+ expect(result.error[:message]).to include("Height must be positive integer")
45
+ end
46
+ end
47
+
48
+ context "when destination path is empty" do
49
+ it "returns FailureResult" do
50
+ result = described_class.call(from: src, to: " ", width: 10, height: 10)
51
+
52
+ expect(result.error[:message]).to include("Destination path cannot be empty")
53
+ end
54
+ end
55
+
56
+ context "when MiniMagick processes successfully" do
57
+ let(:image_double) do
58
+ instance_double(MiniMagick::Image, combine_options: nil, write: true)
59
+ end
60
+
61
+ let(:command_double) do
62
+ double("MiniMagick::Command").tap do |d|
63
+ allow(d).to receive(:background)
64
+ allow(d).to receive(:gravity)
65
+ allow(d).to receive(:thumbnail)
66
+ allow(d).to receive(:extent)
67
+ allow(d).to receive(:unsharp)
68
+ allow(d).to receive(:quality)
69
+ end
70
+ end
71
+
72
+ before do
73
+ allow(MiniMagick::Image).to receive(:open).with(src).and_return(image_double)
74
+ allow(image_double).to receive(:combine_options).and_yield(command_double)
75
+ end
76
+
77
+ it "returns SuccessResult with the destination path" do
78
+ result = described_class.call(from: src, to: dst, width: 80, height: 60)
79
+
80
+ expect(result.value[:path]).to eq(dst)
81
+ end
82
+ end
83
+
84
+ context "when MiniMagick raises an error" do
85
+ before do
86
+ allow(MiniMagick::Image).to receive(:open).and_raise(StandardError, "boom")
87
+ end
88
+
89
+ it "returns FailureResult with error details" do
90
+ result = described_class.call(from: src, to: dst, width: 80, height: 60)
91
+
92
+ expect(result.error[:message]).to include("Image resize failed: boom")
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Utils::PathHelper do
6
+ describe ".path_to_url" do
7
+ it "strips leading public/ and prefixes with /" do
8
+ expect(described_class.path_to_url("public/system/nameplate/1/T/226_95_81/64.png"))
9
+ .to eq("/system/nameplate/1/T/226_95_81/64.png")
10
+ end
11
+
12
+ it "returns string unchanged if it does not start with public/" do
13
+ expect(described_class.path_to_url("/system/nameplate/a.png")).to eq("/system/nameplate/a.png")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NamePlate::ViewHelpers::AvatarHelper do
6
+ let(:dummy_view) do
7
+ Class.new do
8
+ include NamePlate::ViewHelpers::AvatarHelper
9
+ end.new
10
+ end
11
+
12
+ before do
13
+ allow(NamePlate::Avatar).to receive(:generate).and_return("public/system/nameplate/1/T/226_95_81/64.png")
14
+ allow(NamePlate).to receive(:path_to_url).and_call_original
15
+ end
16
+
17
+ describe "#nameplatefor" do
18
+ it "delegates to NamePlate::Avatar.generate" do
19
+ expect(NamePlate::Avatar).to receive(:generate).with("Tony", 64)
20
+ dummy_view.nameplate_for("Tony", 64)
21
+ end
22
+ end
23
+
24
+ describe "#nameplate_url" do
25
+ it "returns URL path derived from generated path" do
26
+ url = dummy_view.nameplate_url("Tony", 64)
27
+ expect(url).to eq("/system/nameplate/1/T/226_95_81/64.png")
28
+ end
29
+ end
30
+
31
+ describe "#nameplate_tag" do
32
+ it "renders a plain img tag when AssetTagHelper is not defined" do
33
+ hide_const("ActionView::Helpers::AssetTagHelper") if defined?(ActionView::Helpers::AssetTagHelper)
34
+ html = dummy_view.nameplate_tag("Tony", 64, class: "avatar")
35
+ expect(html).to eq('<img alt="Tony" class="avatar" src="/system/nameplate/1/T/226_95_81/64.png" />')
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NamePlate do
6
+ describe ".setup" do
7
+ around do |example|
8
+ original = {
9
+ cache_base_path: described_class.cache_base_path,
10
+ pointsize: described_class.pointsize,
11
+ weight: described_class.weight,
12
+ annotate_pos: described_class.annotate_position
13
+ }
14
+
15
+ example.run
16
+ ensure
17
+ described_class.setup do |c|
18
+ c.cache_base_path = original[:cache_base_path]
19
+ c.pointsize = original[:pointsize]
20
+ c.weight = original[:weight]
21
+ c.annotate_position = original[:annotate_pos]
22
+ end
23
+ end
24
+
25
+ it "yields the NamePlate module" do
26
+ expect { |blk| described_class.setup(&blk) }.to yield_with_args(described_class)
27
+ end
28
+
29
+ it "applies configuration set inside the block" do
30
+ described_class.setup { |c| c.cache_base_path = "tmp/system" }
31
+ expect(described_class.cache_base_path).to eq("tmp/system")
32
+ end
33
+ end
34
+
35
+ describe ".generate" do
36
+ it "delegates to Avatar.generate with the same arguments" do
37
+ expect(NamePlate::Avatar).to receive(:generate).with("Tony", 128).and_return("path/to/avatar.png")
38
+ described_class.generate("Tony", 128)
39
+ end
40
+
41
+ it "returns the value from Avatar.generate" do
42
+ allow(NamePlate::Avatar).to receive(:generate).and_return("path/to/avatar.png")
43
+ expect(described_class.generate("Tony", 128)).to eq("path/to/avatar.png")
44
+ end
45
+ end
46
+
47
+ describe ".resize_image" do
48
+ let(:src) { File.expand_path("../../fixtures/src.png", __dir__) }
49
+ let(:dst) { File.expand_path("../../tmp/out.png", __dir__) }
50
+ let(:width) { 200 }
51
+ let(:height) { 200 }
52
+ let(:resize) { NamePlate::Image::Resize }
53
+ let(:success) { NamePlate::Results::SuccessResult.new(value: {path: "out.png"}) }
54
+
55
+ before do
56
+ FileUtils.mkdir_p(File.dirname(src))
57
+ File.write(src, "PNG") unless File.exist?(src)
58
+
59
+ FileUtils.mkdir_p(File.dirname(dst))
60
+ end
61
+
62
+ after do
63
+ FileUtils.rm_f(src)
64
+ FileUtils.rm_f(dst)
65
+ end
66
+
67
+ it "instantiates Image::Resize and calls #resize with keyword args" do
68
+ expect(resize).to receive(:new).with(from: src, to: dst, width:, height:).and_return(resize)
69
+ expect(resize).to receive(:resize!).and_return(success)
70
+ described_class.resize_image(from: src, to: dst, width:, height:)
71
+ end
72
+
73
+ it "returns the result object from Image::Resize#resize" do
74
+ allow(resize).to receive(:call).and_return(success)
75
+ expect(
76
+ described_class.resize_image(from: src, to: dst, width:, height:)
77
+ ).to eq(success)
78
+ end
79
+ end
80
+
81
+ describe ".resize (legacy)" do
82
+ let(:success) { NamePlate::Results::SuccessResult.new(value: {path: "out.png"}) }
83
+ let(:failure) { NamePlate::Results::FailureResult.new(error: {message: "nope"}) }
84
+
85
+ it "returns true when resize_image returns SuccessResult" do
86
+ allow(described_class).to receive(:resize_image).and_return(success)
87
+ expect(described_class.resize_image("in.png", "out.png", 50, 50)).to be(success)
88
+ end
89
+
90
+ it "returns false when resize_image returns FailureResult" do
91
+ allow(described_class).to receive(:resize_image).and_return(failure)
92
+ expect(described_class.resize_image("in.png", "out.png", 50, 50)).to be(failure)
93
+ end
94
+ end
95
+
96
+ describe ".path_to_url" do
97
+ it "converts a public filesystem path to a URL path" do
98
+ expect(described_class.path_to_url("public/system/nameplate/1/T/226_95_81/64.png"))
99
+ .to eq("/system/nameplate/1/T/226_95_81/64.png")
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nameplate"
4
+ require "pry"
5
+ require "pry-byebug"
6
+
7
+ RSpec.configure do |config|
8
+ config.example_status_persistence_file_path = ".rspec_status"
9
+ config.disable_monkey_patching!
10
+ config.expect_with :rspec do |c|
11
+ c.syntax = :expect
12
+ end
13
+ end