spritely 0.0.1 → 0.0.2

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spritely/collection.rb +58 -0
  3. data/lib/spritely/generators/chunky_png.rb +1 -1
  4. data/lib/spritely/image.rb +2 -17
  5. data/lib/spritely/image_set.rb +52 -0
  6. data/lib/spritely/options.rb +19 -0
  7. data/lib/spritely/sass_functions.rb +8 -6
  8. data/lib/spritely/sprite_map.rb +17 -12
  9. data/lib/spritely/sprockets/manifest.rb +13 -0
  10. data/lib/spritely/version.rb +1 -1
  11. data/lib/spritely.rb +1 -2
  12. data/spec/fixtures/correct-sprite.png +0 -0
  13. data/spec/fixtures/rails-app/app/assets/images/application/background.png +0 -0
  14. data/spec/fixtures/rails-app/app/assets/images/application/fool.png +0 -0
  15. data/spec/fixtures/rails-app/app/assets/images/application/football.png +0 -0
  16. data/spec/fixtures/rails-app/app/assets/images/application/mario.png +0 -0
  17. data/spec/fixtures/rails-app/app/assets/stylesheets/sprites.css.scss +13 -0
  18. data/spec/integration/application.png +0 -0
  19. data/spec/integration/precompilation_spec.rb +29 -0
  20. data/spec/integration/stylesheet_spec.rb +28 -0
  21. data/spec/spec_helper.rb +13 -2
  22. data/spec/spritely/collection_spec.rb +91 -0
  23. data/spec/spritely/generators/chunky_png_spec.rb +0 -1
  24. data/spec/spritely/image_set_spec.rb +85 -0
  25. data/spec/spritely/image_spec.rb +9 -7
  26. data/spec/spritely/options_spec.rb +16 -0
  27. data/spec/spritely/sprite_map_spec.rb +24 -6
  28. data/spec/support/rails_app_helpers.rb +33 -0
  29. data/spec/support/shared_examples.rb +2 -13
  30. metadata +67 -6
  31. data/lib/spritely/images_set.rb +0 -41
  32. data/spec/spritely/images_set_spec.rb +0 -54
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d722c5631f45aecb25c733b67c27477cabb3087b
4
- data.tar.gz: cacdcf82d6ec8c69a7916443c076ec2cb3f6471d
3
+ metadata.gz: f9409fd4cb51f3dc5d07f61de99861f9b9dbb12f
4
+ data.tar.gz: 9390ecc20122b1106bed57f394e0f93fd6a60040
5
5
  SHA512:
6
- metadata.gz: 47639393b85c3b0a5bf61103de2d2ccbfd68fc9facae1a21e97b1c492f71edb6e3374e2379cf5af5f48e03605475da54f2caea7500ff001c5039d1bdbf413bdd
7
- data.tar.gz: d2e6d843569997d59e82ee7228dea3cd494b2f7e0ad2dba5d2313c6ff34026dd30e3fe97154224a95d9c6f4cd0b10306e1e3482b9ea44b5403d2535038cf20f0
6
+ metadata.gz: e639ea7030383d04cd530f4cfcef3e454ff764ebf7f5b625916ba03a99273ae05100473f5ca6e70ef20b3d161fed72d5d4f0c233a70cf6b39dfd37e99c571007
7
+ data.tar.gz: 2634ff4658e0d9d62a4d307ce7a89ba4e0f1fd53cb0190db39c7e2a91adb4f63dc260e7f8f7523497292795fdf6d9a8589e5bcc0caa53c2723c1db7be846ef18
@@ -0,0 +1,58 @@
1
+ require 'spritely/image_set'
2
+
3
+ module Spritely
4
+ class Collection < Struct.new(:files, :options)
5
+ def self.create(*args)
6
+ new(*args).tap do |collection|
7
+ collection.position!
8
+ end
9
+ end
10
+
11
+ def images
12
+ image_sets.flat_map(&:images)
13
+ end
14
+
15
+ def find(name)
16
+ image_sets.find { |image_set| image_set.name == name }
17
+ end
18
+
19
+ def last_modification_time
20
+ files.collect { |file| Spritely.modification_time(file) }.max
21
+ end
22
+
23
+ def width
24
+ return @width if @width
25
+
26
+ max_width = image_sets.collect(&:width).max
27
+ if image_sets.none?(&:repeated?)
28
+ @width = max_width
29
+ else
30
+ @width = lcm = image_sets.select(&:repeated?).collect(&:width).reduce(:lcm)
31
+ @width += lcm while @width < max_width
32
+ end
33
+
34
+ @width
35
+ end
36
+
37
+ def height
38
+ heights.reduce(:+)
39
+ end
40
+
41
+ def position!
42
+ image_sets.each_with_index do |image_set, index|
43
+ image_set.top = heights[0..index].reduce(:+) - image_set.height
44
+ image_set.position_in!(width)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def image_sets
51
+ @image_sets ||= files.collect { |file| ImageSet.new(file, options[File.basename(file, ".png")]) }.sort_by(&:width).reverse
52
+ end
53
+
54
+ def heights
55
+ image_sets.collect(&:height)
56
+ end
57
+ end
58
+ end
@@ -18,7 +18,7 @@ module Spritely
18
18
  private
19
19
 
20
20
  def canvas
21
- @canvas ||= ::ChunkyPNG::Image.new(sprite_map.images.max_width, sprite_map.images.total_height)
21
+ @canvas ||= ::ChunkyPNG::Image.new(sprite_map.width, sprite_map.height)
22
22
  end
23
23
  end
24
24
  end
@@ -1,20 +1,5 @@
1
1
  module Spritely
2
- class Image
3
- attr_accessor :top
4
- attr_reader :path, :data, :width, :height
5
-
6
- def initialize(path)
7
- @path = path
8
- @data = File.read(path)
9
- @width, @height = data[0x10..0x18].unpack('NN')
10
- end
11
-
12
- def left
13
- 0
14
- end
15
-
16
- def name
17
- File.basename(path, ".png")
18
- end
2
+ class Image < Struct.new(:data)
3
+ attr_accessor :top, :left
19
4
  end
20
5
  end
@@ -0,0 +1,52 @@
1
+ require 'spritely/image'
2
+
3
+ module Spritely
4
+ class ImageSet
5
+ attr_accessor :top
6
+ attr_reader :path, :options, :data, :width, :height
7
+
8
+ def initialize(path, options)
9
+ @path = path
10
+ @options = options
11
+ @data = File.read(path)
12
+ @width, @height = data[0x10..0x18].unpack('NN')
13
+ end
14
+
15
+ def name
16
+ File.basename(path, ".png")
17
+ end
18
+
19
+ def images
20
+ @images ||= []
21
+ end
22
+
23
+ def left
24
+ 0
25
+ end
26
+
27
+ def repeated?
28
+ !!options[:repeat]
29
+ end
30
+
31
+ def position_in!(collection_width)
32
+ if repeated?
33
+ left = 0
34
+ while left < collection_width
35
+ add_image!(left)
36
+ left += width
37
+ end
38
+ else
39
+ add_image!(0)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def add_image!(left)
46
+ images << Image.new(data).tap do |image|
47
+ image.top = top
48
+ image.left = left
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_support/core_ext/hash/deep_merge'
2
+
3
+ module Spritely
4
+ class Options
5
+ attr_reader :options
6
+
7
+ def initialize(hash)
8
+ @options = hash.inject({}) do |h, (key, value)|
9
+ split_key = key.split('_')
10
+ option = {split_key.pop.to_sym => value}
11
+ h.deep_merge!(split_key.join('-') => option)
12
+ end
13
+ end
14
+
15
+ def [](key)
16
+ options[key] || {}
17
+ end
18
+ end
19
+ end
@@ -2,11 +2,11 @@ require 'spritely/sprite_map'
2
2
 
3
3
  module Spritely
4
4
  module SassFunctions
5
- def sprite_map(glob)
6
- SpriteMap.create(glob)
5
+ def sprite_map(glob, kwargs = {})
6
+ SpriteMap.create(glob, kwargs)
7
7
  end
8
8
 
9
- ::Sass::Script::Functions.declare :sprite_map, [:glob]
9
+ ::Sass::Script::Functions.declare :sprite_map, [:glob], var_kwargs: true
10
10
 
11
11
  def sprite_url(sprite_map)
12
12
  asset_url(Sass::Script::String.new("sprites/#{sprite_map.name}.png"))
@@ -15,7 +15,7 @@ module Spritely
15
15
  ::Sass::Script::Functions.declare :sprite_url, [:sprite_map]
16
16
 
17
17
  def sprite_position(sprite_map, image_name)
18
- image = sprite_map.images.find(image_name.value)
18
+ image = sprite_map.find(image_name.value)
19
19
 
20
20
  x = Sass::Script::Number.new(image.left, image.left == 0 ? [] : ['px'])
21
21
  y = Sass::Script::Number.new(-image.top, image.top == 0 ? [] : ['px'])
@@ -26,7 +26,7 @@ module Spritely
26
26
  ::Sass::Script::Functions.declare :sprite_position, [:sprite_map, :image_name]
27
27
 
28
28
  def sprite_width(sprite_map, image_name)
29
- image = sprite_map.images.find(image_name.value)
29
+ image = sprite_map.find(image_name.value)
30
30
 
31
31
  Sass::Script::Number.new(image.width, ['px'])
32
32
  end
@@ -34,7 +34,7 @@ module Spritely
34
34
  ::Sass::Script::Functions.declare :sprite_width, [:sprite_map, :image_name]
35
35
 
36
36
  def sprite_height(sprite_map, image_name)
37
- image = sprite_map.images.find(image_name.value)
37
+ image = sprite_map.find(image_name.value)
38
38
 
39
39
  Sass::Script::Number.new(image.height, ['px'])
40
40
  end
@@ -42,3 +42,5 @@ module Spritely
42
42
  ::Sass::Script::Functions.declare :sprite_height, [:sprite_map, :image_name]
43
43
  end
44
44
  end
45
+
46
+ ::Sass::Script::Functions.send(:include, Spritely::SassFunctions)
@@ -1,10 +1,14 @@
1
- require 'spritely/images_set'
1
+ require 'spritely/options'
2
+ require 'spritely/collection'
2
3
  require 'spritely/generators/chunky_png'
3
4
 
4
5
  module Spritely
5
- class SpriteMap
6
- attr_accessor :options # Sass expects an object returned from a script function to have an #options accessor
7
- attr_reader :glob
6
+ class SpriteMap < Sass::Script::Literal
7
+ extend Forwardable
8
+
9
+ def_delegators :collection, :find, :width, :height, :images
10
+
11
+ attr_reader :glob, :options
8
12
 
9
13
  def self.create(*args)
10
14
  new(*args).tap do |sprite_map|
@@ -12,12 +16,17 @@ module Spritely
12
16
  end
13
17
  end
14
18
 
15
- def initialize(glob)
19
+ def initialize(glob, options = {})
16
20
  @glob = glob.value
21
+ @options = Options.new(options)
22
+ end
23
+
24
+ def inspect
25
+ "#<Spritely::SpriteMap name=#{name} filename=#{filename}>"
17
26
  end
18
27
 
19
- def images
20
- @images ||= ImagesSet.new(files)
28
+ def collection
29
+ @collection ||= Collection.create(files, options)
21
30
  end
22
31
 
23
32
  def generate!
@@ -43,11 +52,7 @@ module Spritely
43
52
  end
44
53
 
45
54
  def outdated?
46
- images.last_modification_time > modification_time
47
- end
48
-
49
- def modification_time
50
- Spritely.modification_time(filename)
55
+ collection.last_modification_time > Spritely.modification_time(filename)
51
56
  end
52
57
  end
53
58
  end
@@ -0,0 +1,13 @@
1
+ require 'sprockets/manifest'
2
+ require 'active_support/core_ext/module/aliasing'
3
+
4
+ module Sprockets
5
+ class Manifest
6
+ def compile_with_sprites(*args)
7
+ compile_without_sprites(*args)
8
+ Dir.glob(Spritely.directory.join('*.png')).each(&method(:compile_without_sprites))
9
+ end
10
+
11
+ alias_method_chain :compile, :sprites
12
+ end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module Spritely
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/spritely.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'sass'
2
2
  require 'spritely/sass_functions'
3
+ require 'spritely/sprockets/manifest'
3
4
 
4
5
  raise LoadError, "Spritely cannot be used in conjunction with Compass. Hope you choose Spritely!" if defined?(::Compass)
5
6
 
@@ -16,5 +17,3 @@ module Spritely
16
17
  File.mtime(filename).to_i
17
18
  end
18
19
  end
19
-
20
- ::Sass::Script::Functions.send(:include, Spritely::SassFunctions)
Binary file
@@ -0,0 +1,13 @@
1
+ $application-sprite: sprite-map("application/*.png", $background-repeat: true);
2
+
3
+ body {
4
+ background-image: sprite-url($application-sprite);
5
+ background-position: sprite-position($application-sprite, "background");
6
+ }
7
+
8
+ #mario {
9
+ background-image: sprite-url($application-sprite);
10
+ background-position: sprite-position($application-sprite, "mario");
11
+ width: sprite-width($application-sprite, "mario");
12
+ height: sprite-height($application-sprite, "mario");
13
+ }
Binary file
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'active_support/core_ext/string'
3
+
4
+ describe 'Precompilation', :integration do
5
+ it 'should create the sprite in the right location' do
6
+ render_asset('sprites.css')
7
+ expect(File.exist?(File.join('app', 'assets', 'images', 'sprites', 'application.png'))).to be_true
8
+ end
9
+
10
+ it 'should compile the correct-looking sprite file' do
11
+ render_asset('sprites.css')
12
+ compiled_sprite = ChunkyPNG::Image.from_file(File.join('app', 'assets', 'images', 'sprites', 'application.png'))
13
+ correct_sprite = ChunkyPNG::Image.from_file(File.join(__dir__, '..', 'fixtures', 'correct-sprite.png'))
14
+ expect(compiled_sprite).to eq(correct_sprite)
15
+ end
16
+
17
+ it 'should compile all of the assets necessary' do
18
+ compile_assets
19
+ sprite_files = Dir.glob(File.join('public', 'assets', 'sprites', 'application-*.png'))
20
+ expect(sprite_files).to have(1).item
21
+ end
22
+
23
+ it 'should compile all of the assets necessary when sprites have been pre-generated' do
24
+ render_asset('sprites.css')
25
+ compile_assets
26
+ sprite_files = Dir.glob(File.join('public', 'assets', 'sprites', 'application-*.png'))
27
+ expect(sprite_files).to have(1).item
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require 'active_support/core_ext/string/strip'
3
+
4
+ describe 'Stylesheet generation', :integration do
5
+ subject(:stylesheet) { render_asset('sprites.css') }
6
+
7
+ describe 'body CSS with repetition' do
8
+ it { should include(<<-CSS.strip_heredoc
9
+ body {
10
+ background-image: url(/assets/sprites/application.png);
11
+ background-position: 0 -837px;
12
+ }
13
+ CSS
14
+ ) }
15
+ end
16
+
17
+ describe '#mario CSS with no repetition' do
18
+ it { should include(<<-CSS.strip_heredoc
19
+ #mario {
20
+ background-image: url(/assets/sprites/application.png);
21
+ background-position: 0 -623px;
22
+ width: 200px;
23
+ height: 214px;
24
+ }
25
+ CSS
26
+ ) }
27
+ end
28
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,16 @@
1
1
  require 'rspec'
2
- require 'pry-byebug'
3
2
  require 'spritely'
4
3
 
5
- Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
4
+ require 'pry-byebug'
5
+
6
+ Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
7
+
8
+ RSpec.configure do |config|
9
+ config.expect_with :rspec do |c|
10
+ c.syntax = :expect
11
+ end
12
+ config.treat_symbols_as_metadata_keys_with_true_values = true
13
+
14
+ config.include RailsAppHelpers, :integration
15
+ config.around(:each, :integration) { |example| within_rails_app(&example) }
16
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spritely::Collection do
4
+ let(:first_set) { double(repeated?: true, name: 'foo', width: 1, height: 10, images: [1]) }
5
+ let(:second_set) { double(repeated?: false, name: 'bar', width: 100, height: 100, images: [2, 3]) }
6
+
7
+ subject { Spritely::Collection.new(['file-1.png', 'file-2.png'], {'file-1' => {repeat: true}}) }
8
+
9
+ before do
10
+ allow(Spritely::ImageSet).to receive(:new).with('file-1.png', repeat: true).and_return(first_set)
11
+ allow(Spritely::ImageSet).to receive(:new).with('file-2.png', nil).and_return(second_set)
12
+ end
13
+
14
+ its(:files) { should eq(['file-1.png', 'file-2.png']) }
15
+ its(:options) { should eq({'file-1' => {repeat: true}}) }
16
+ its(:images) { should eq([2, 3, 1]) }
17
+ its(:height) { should eq(110) }
18
+
19
+ describe '.create' do
20
+ let(:collection) { double }
21
+
22
+ it 'should attempt to generate the sprite' do
23
+ allow(Spritely::Collection).to receive(:new).with('something').and_return(collection)
24
+ expect(collection).to receive(:position!)
25
+ Spritely::Collection.create('something')
26
+ end
27
+ end
28
+
29
+ describe '#width' do
30
+ let(:third_set) { double(repeated?: false, width: 65, height: 100) }
31
+
32
+ subject { Spritely::Collection.new(['file-1.png', 'file-2.png', 'file-3.png'], {}) }
33
+
34
+ before do
35
+ allow(Spritely::ImageSet).to receive(:new).with('file-1.png', nil).and_return(first_set)
36
+ allow(Spritely::ImageSet).to receive(:new).with('file-2.png', nil).and_return(second_set)
37
+ allow(Spritely::ImageSet).to receive(:new).with('file-3.png', nil).and_return(third_set)
38
+ end
39
+
40
+ its(:width) { should eq(100) }
41
+
42
+ context 'when the repetition will overflow the largest non-repeating image' do
43
+ let(:first_set) { double(repeated?: true, width: 12, height: 10) }
44
+ let(:second_set) { double(repeated?: false, width: 100, height: 100) }
45
+
46
+ its(:width) { should eq(108) }
47
+ end
48
+
49
+ context 'when there are multiple repeating images, find the lcm' do
50
+ let(:first_set) { double(repeated?: true, width: 12, height: 10) }
51
+ let(:second_set) { double(repeated?: true, width: 100, height: 100) }
52
+ let(:third_set) { double(repeated?: true, width: 65, height: 100) }
53
+
54
+ its(:width) { should eq(3900) }
55
+ end
56
+
57
+ context 'when there is a mix of repeating and non-repeating' do
58
+ let(:first_set) { double(repeated?: true, width: 12, height: 10) }
59
+ let(:second_set) { double(repeated?: false, width: 100, height: 100) }
60
+ let(:third_set) { double(repeated?: true, width: 5, height: 100) }
61
+
62
+ its(:width) { should eq(120) }
63
+ end
64
+ end
65
+
66
+ describe '#find' do
67
+ it 'should find the correct set by name' do
68
+ expect(subject.find('foo')).to eq(first_set)
69
+ expect(subject.find('bar')).to eq(second_set)
70
+ end
71
+ end
72
+
73
+ describe '#last_modification_time' do
74
+ before do
75
+ allow(Spritely).to receive(:modification_time).with('file-1.png').and_return(10)
76
+ allow(Spritely).to receive(:modification_time).with('file-2.png').and_return(100)
77
+ end
78
+
79
+ its(:last_modification_time) { should eq(100) }
80
+ end
81
+
82
+ describe '#position!' do
83
+ it 'should call out to each image set in turn' do
84
+ expect(second_set).to receive(:top=).with(0)
85
+ expect(second_set).to receive(:position_in!).with(100)
86
+ expect(first_set).to receive(:top=).with(100)
87
+ expect(first_set).to receive(:position_in!).with(100)
88
+ subject.position!
89
+ end
90
+ end
91
+ end
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'ostruct'
3
2
 
4
3
  describe Spritely::Generators::ChunkyPng do
5
4
  let(:png_canvas) { double }
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe Spritely::ImageSet do
5
+ let(:path) { "#{__dir__}/../fixtures/test/foo.png" }
6
+ let(:options) { {repeat: true} }
7
+
8
+ subject { Spritely::ImageSet.new(path, options) }
9
+
10
+ its(:path) { should eq(path) }
11
+ its(:options) { should eq(options) }
12
+ its(:data) { should eq(File.read(path)) }
13
+ its(:width) { should eq(1) }
14
+ its(:height) { should eq(1) }
15
+ its(:name) { should eq('foo') }
16
+ its(:left) { should eq(0) }
17
+
18
+ describe '#top' do
19
+ before { subject.top = 123 }
20
+
21
+ its(:top) { should eq(123) }
22
+ end
23
+
24
+ describe '#repeated?' do
25
+ it { should be_repeated }
26
+
27
+ context 'repeat option is passed as truthy' do
28
+ let(:options) { {repeat: 'repeat'} }
29
+
30
+ it { should be_repeated }
31
+ end
32
+
33
+ context 'repeat option is passed as false' do
34
+ let(:options) { {repeat: false} }
35
+
36
+ it { should_not be_repeated }
37
+ end
38
+
39
+ context 'repeat option is not passed' do
40
+ let(:options) { {} }
41
+
42
+ it { should_not be_repeated }
43
+ end
44
+ end
45
+
46
+ describe '#position_in!' do
47
+ class ImageDouble
48
+ attr_accessor :top, :left
49
+ end
50
+
51
+ before { subject.top = 123 }
52
+
53
+ context 'the image is repeated' do
54
+ let(:first_image) { ImageDouble.new }
55
+ let(:second_image) { ImageDouble.new }
56
+
57
+ before do
58
+ allow(Spritely::Image).to receive(:new).with(File.read(path)).and_return(first_image, second_image)
59
+ subject.position_in!(2)
60
+ end
61
+
62
+ it 'should set the position of the images' do
63
+ expect(first_image.top).to eq(123)
64
+ expect(first_image.left).to eq(0)
65
+ expect(second_image.top).to eq(123)
66
+ expect(second_image.left).to eq(1)
67
+ end
68
+ end
69
+
70
+ context 'the image is not repeated' do
71
+ let(:options) { {repeat: false} }
72
+ let(:image) { ImageDouble.new }
73
+
74
+ before do
75
+ allow(Spritely::Image).to receive(:new).with(File.read(path)).and_return(image)
76
+ subject.position_in!(1)
77
+ end
78
+
79
+ it 'should set the position of the image' do
80
+ expect(image.top).to eq(123)
81
+ expect(image.left).to eq(0)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -2,19 +2,21 @@ require 'spec_helper'
2
2
 
3
3
  describe Spritely::Image do
4
4
  let(:path) { "#{__dir__}/../fixtures/test/foo.png" }
5
+ let(:data) { File.read(path) }
5
6
 
6
- subject { Spritely::Image.new(path) }
7
+ subject { Spritely::Image.new(data) }
7
8
 
8
- its(:path) { should eq(path) }
9
- its(:data) { should eq(File.read(path)) }
10
- its(:width) { should eq(1) }
11
- its(:height) { should eq(1) }
12
- its(:left) { should eq(0) }
13
- its(:name) { should eq('foo') }
9
+ its(:data) { should eq(data) }
14
10
 
15
11
  describe '#top' do
16
12
  before { subject.top = 123 }
17
13
 
18
14
  its(:top) { should eq(123) }
19
15
  end
16
+
17
+ describe '#left' do
18
+ before { subject.left = 456 }
19
+
20
+ its(:left) { should eq(456) }
21
+ end
20
22
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spritely::Options do
4
+ let(:hash) { {'some_new_image_x' => 123, 'some_new_image_y' => 456, 'another_image_repeat' => true} }
5
+
6
+ subject(:options) { Spritely::Options.new(hash) }
7
+
8
+ its(['some-new-image']) { should eq({x: 123, y: 456}) }
9
+ its(['another-image']) { should eq({repeat: true}) }
10
+
11
+ describe '#[]' do
12
+ it 'should fall back to an empty hash' do
13
+ expect(options[:unknown]).to eq({})
14
+ end
15
+ end
16
+ end
@@ -1,13 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Spritely::SpriteMap do
4
- subject { Spritely::SpriteMap.new(double(value: 'test/*.png')) }
4
+ let(:options) { {'some_new_image_x' => 123, 'some_new_image_y' => 456, 'another_image_repeat' => true} }
5
5
 
6
- before { Spritely.stub(:directory).and_return(File) }
6
+ subject { Spritely::SpriteMap.new(double(value: 'test/*.png'), options) }
7
+
8
+ before do
9
+ Spritely.stub(:directory).and_return(File)
10
+ allow(Spritely::Options).to receive(:new).with(options).and_return('options')
11
+ end
12
+
13
+ it { should be_a(Sass::Script::Literal) }
7
14
 
8
15
  its(:glob) { should eq('test/*.png') }
16
+ its(:options) { should eq('options') }
9
17
  its(:name) { should eq('test') }
10
18
  its(:filename) { should eq('test.png') }
19
+ its(:inspect) { should eq('#<Spritely::SpriteMap name=test filename=test.png>') }
11
20
 
12
21
  describe '.create' do
13
22
  let(:sprite_map) { double(needs_generation?: true) }
@@ -19,13 +28,22 @@ describe Spritely::SpriteMap do
19
28
  end
20
29
  end
21
30
 
22
- describe '#images' do
31
+ describe '#collection' do
32
+ let(:collection) { double(find: 'find value', width: 'width value', height: 'height value', images: 'images value') }
33
+
23
34
  before do
24
35
  Spritely.stub_chain(:environment, :paths).and_return(["#{__dir__}/../fixtures"])
25
- allow(Spritely::ImagesSet).to receive(:new).with(["#{__dir__}/../fixtures/test/foo.png"]).and_return('images set')
36
+ allow(Spritely::Collection).to receive(:create).with(["#{__dir__}/../fixtures/test/foo.png"], 'options').and_return(collection)
26
37
  end
27
38
 
28
- its(:images) { should eq('images set') }
39
+ its(:collection) { should eq(collection) }
40
+
41
+ describe 'delegated methods' do
42
+ its(:find) { should eq('find value') }
43
+ its(:width) { should eq('width value') }
44
+ its(:height) { should eq('height value') }
45
+ its(:images) { should eq('images value') }
46
+ end
29
47
  end
30
48
 
31
49
  describe '#generate!' do
@@ -46,7 +64,7 @@ describe Spritely::SpriteMap do
46
64
  let(:file_exists) { true }
47
65
 
48
66
  before do
49
- subject.stub_chain(:images, :last_modification_time).and_return(456)
67
+ subject.stub_chain(:collection, :last_modification_time).and_return(456)
50
68
  allow(Spritely).to receive(:modification_time).and_return(123)
51
69
  end
52
70
 
@@ -0,0 +1,33 @@
1
+ module RailsAppHelpers
2
+ def within_rails_app(&block)
3
+ Dir.mktmpdir do |tmpdir|
4
+ Dir.chdir(tmpdir) do
5
+ %x(rails new dummy --skip-active-record --skip-test-unit --skip-javascript --skip-spring --skip-git)
6
+ Dir.chdir('dummy') do
7
+ Bundler.with_clean_env do
8
+ File.open('Gemfile', 'a') do |f|
9
+ f.write("gem 'spritely', path: '#{__dir__}/../../'\n")
10
+ end
11
+ %x(bundle install)
12
+ FileUtils.cp_r "#{__dir__}/../fixtures/rails-app/.", "."
13
+ yield
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ def render_asset(filename)
21
+ runner "puts Rails.application.assets[#{filename.inspect}]"
22
+ end
23
+
24
+ def compile_assets
25
+ %x(RAILS_ENV=production rake assets:precompile)
26
+ end
27
+
28
+ private
29
+
30
+ def runner(command)
31
+ %x(rails runner -e development '#{command}')
32
+ end
33
+ end
@@ -1,17 +1,6 @@
1
1
  shared_examples "a generator" do
2
- class ImagesDouble
3
- def max_width; 100; end
4
- def total_height; 200; end
5
-
6
- def each(&block)
7
- [
8
- OpenStruct.new(data: 'first image data', left: 1, top: 10),
9
- OpenStruct.new(data: 'second image data', left: 2, top: 20)
10
- ].each(&block)
11
- end
12
- end
13
-
14
- let(:sprite_map) { double(images: ImagesDouble.new, filename: 'blah.png') }
2
+ let(:images) { [OpenStruct.new(data: 'first image data', left: 1, top: 10), OpenStruct.new(data: 'second image data', left: 2, top: 20)] }
3
+ let(:sprite_map) { double(images: images, width: 100, height: 200, filename: 'blah.png') }
15
4
 
16
5
  its(:sprite_map) { should eq(sprite_map) }
17
6
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spritely
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Robbin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-12 00:00:00.000000000 Z
11
+ date: 2014-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chunky_png
@@ -24,6 +24,26 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: railties
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 4.0.0
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '5.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 4.0.0
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.0'
27
47
  - !ruby/object:Gem::Dependency
28
48
  name: sass
29
49
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +86,20 @@ dependencies:
66
86
  - - ">="
67
87
  - !ruby/object:Gem::Version
68
88
  version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rake
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
69
103
  - !ruby/object:Gem::Dependency
70
104
  name: rspec
71
105
  requirement: !ruby/object:Gem::Requirement
@@ -81,7 +115,7 @@ dependencies:
81
115
  - !ruby/object:Gem::Version
82
116
  version: '0'
83
117
  - !ruby/object:Gem::Dependency
84
- name: rake
118
+ name: appraisal
85
119
  requirement: !ruby/object:Gem::Requirement
86
120
  requirements:
87
121
  - - ">="
@@ -102,20 +136,35 @@ extensions: []
102
136
  extra_rdoc_files: []
103
137
  files:
104
138
  - lib/spritely.rb
139
+ - lib/spritely/collection.rb
105
140
  - lib/spritely/generators/base.rb
106
141
  - lib/spritely/generators/chunky_png.rb
107
142
  - lib/spritely/image.rb
108
- - lib/spritely/images_set.rb
143
+ - lib/spritely/image_set.rb
144
+ - lib/spritely/options.rb
109
145
  - lib/spritely/sass_functions.rb
110
146
  - lib/spritely/sprite_map.rb
147
+ - lib/spritely/sprockets/manifest.rb
111
148
  - lib/spritely/version.rb
149
+ - spec/fixtures/correct-sprite.png
150
+ - spec/fixtures/rails-app/app/assets/images/application/background.png
151
+ - spec/fixtures/rails-app/app/assets/images/application/fool.png
152
+ - spec/fixtures/rails-app/app/assets/images/application/football.png
153
+ - spec/fixtures/rails-app/app/assets/images/application/mario.png
154
+ - spec/fixtures/rails-app/app/assets/stylesheets/sprites.css.scss
112
155
  - spec/fixtures/test/foo.png
156
+ - spec/integration/application.png
157
+ - spec/integration/precompilation_spec.rb
158
+ - spec/integration/stylesheet_spec.rb
113
159
  - spec/spec_helper.rb
160
+ - spec/spritely/collection_spec.rb
114
161
  - spec/spritely/generators/chunky_png_spec.rb
162
+ - spec/spritely/image_set_spec.rb
115
163
  - spec/spritely/image_spec.rb
116
- - spec/spritely/images_set_spec.rb
164
+ - spec/spritely/options_spec.rb
117
165
  - spec/spritely/sprite_map_spec.rb
118
166
  - spec/spritely_spec.rb
167
+ - spec/support/rails_app_helpers.rb
119
168
  - spec/support/shared_examples.rb
120
169
  homepage: http://www.robbinsweb.biz
121
170
  licenses:
@@ -143,11 +192,23 @@ specification_version: 4
143
192
  summary: Hooks into the Rails asset pipeline to allow you to easily generate sprite
144
193
  maps
145
194
  test_files:
195
+ - spec/fixtures/correct-sprite.png
196
+ - spec/fixtures/rails-app/app/assets/images/application/background.png
197
+ - spec/fixtures/rails-app/app/assets/images/application/fool.png
198
+ - spec/fixtures/rails-app/app/assets/images/application/football.png
199
+ - spec/fixtures/rails-app/app/assets/images/application/mario.png
200
+ - spec/fixtures/rails-app/app/assets/stylesheets/sprites.css.scss
146
201
  - spec/fixtures/test/foo.png
202
+ - spec/integration/application.png
203
+ - spec/integration/precompilation_spec.rb
204
+ - spec/integration/stylesheet_spec.rb
147
205
  - spec/spec_helper.rb
206
+ - spec/spritely/collection_spec.rb
148
207
  - spec/spritely/generators/chunky_png_spec.rb
208
+ - spec/spritely/image_set_spec.rb
149
209
  - spec/spritely/image_spec.rb
150
- - spec/spritely/images_set_spec.rb
210
+ - spec/spritely/options_spec.rb
151
211
  - spec/spritely/sprite_map_spec.rb
152
212
  - spec/spritely_spec.rb
213
+ - spec/support/rails_app_helpers.rb
153
214
  - spec/support/shared_examples.rb
@@ -1,41 +0,0 @@
1
- require 'spritely/image'
2
-
3
- module Spritely
4
- class ImagesSet
5
- extend Forwardable
6
-
7
- def_delegator :@images, :each
8
-
9
- attr_reader :files, :images
10
-
11
- def initialize(files)
12
- @files = files
13
- @images = files.collect { |file| Image.new(file) }.sort_by(&:width).reverse
14
- position!
15
- end
16
-
17
- def find(name)
18
- images.find { |image| image.name == name }
19
- end
20
-
21
- def max_width
22
- @max_width ||= images.collect(&:width).max
23
- end
24
-
25
- def total_height
26
- @total_height ||= images.collect(&:height).reduce(:+)
27
- end
28
-
29
- def last_modification_time
30
- files.collect { |file| Spritely.modification_time(file) }.max
31
- end
32
-
33
- private
34
-
35
- def position!
36
- images.each_with_index do |image, index|
37
- image.top = images.collect(&:height)[0..index].reduce(:+) - image.height
38
- end
39
- end
40
- end
41
- end
@@ -1,54 +0,0 @@
1
- require 'spec_helper'
2
- require 'ostruct'
3
-
4
- describe Spritely::ImagesSet do
5
- class ImageDouble < OpenStruct
6
- attr_accessor :top
7
- end
8
-
9
- let(:first_image) { ImageDouble.new(name: 'foo', width: 1, height: 10) }
10
- let(:second_image) { ImageDouble.new(name: 'bar', width: 100, height: 100) }
11
-
12
- before do
13
- allow(Spritely::Image).to receive(:new).with('first').and_return(first_image)
14
- allow(Spritely::Image).to receive(:new).with('second').and_return(second_image)
15
- end
16
-
17
- subject! { Spritely::ImagesSet.new(['first', 'second']) }
18
-
19
- its(:files) { should eq(['first', 'second']) }
20
- its(:images) { should eq([second_image, first_image]) }
21
- its(:max_width) { should eq(100) }
22
- its(:total_height) { should eq(110) }
23
-
24
- describe 'positioning' do
25
- it 'should set the #top position of each image' do
26
- expect(first_image.top).to eq(100)
27
- expect(second_image.top).to eq(0)
28
- end
29
- end
30
-
31
- describe '#each delegation' do
32
- it 'should pass along the #each method call to the internal #images array' do
33
- expect(first_image).to receive(:blah!)
34
- expect(second_image).to receive(:blah!)
35
- subject.each(&:blah!)
36
- end
37
- end
38
-
39
- describe '#find' do
40
- it 'should find the correct image by name' do
41
- expect(subject.find('foo')).to eq(first_image)
42
- expect(subject.find('bar')).to eq(second_image)
43
- end
44
- end
45
-
46
- describe '#last_modification_time' do
47
- before do
48
- allow(Spritely).to receive(:modification_time).with('first').and_return(10)
49
- allow(Spritely).to receive(:modification_time).with('second').and_return(100)
50
- end
51
-
52
- its(:last_modification_time) { should eq(100) }
53
- end
54
- end