inline_svg 1.0.0 → 1.10.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 (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/integration_test.yml +47 -0
  3. data/.github/workflows/rails_6_webpacker_integration_tests.yaml +62 -0
  4. data/.github/workflows/ruby.yml +20 -0
  5. data/.rubocop.yml +1 -0
  6. data/.rubocop_todo.yml +421 -0
  7. data/CHANGELOG.md +143 -3
  8. data/README.md +149 -30
  9. data/Rakefile +7 -0
  10. data/inline_svg.gemspec +2 -1
  11. data/lib/inline_svg/action_view/helpers.rb +68 -7
  12. data/lib/inline_svg/cached_asset_file.rb +71 -0
  13. data/lib/inline_svg/finds_asset_paths.rb +1 -1
  14. data/lib/inline_svg/id_generator.rb +12 -3
  15. data/lib/inline_svg/io_resource.rb +4 -3
  16. data/lib/inline_svg/propshaft_asset_finder.rb +16 -0
  17. data/lib/inline_svg/railtie.rb +8 -3
  18. data/lib/inline_svg/static_asset_finder.rb +5 -2
  19. data/lib/inline_svg/transform_pipeline/transformations/aria_attributes.rb +16 -19
  20. data/lib/inline_svg/transform_pipeline/transformations/aria_hidden.rb +9 -0
  21. data/lib/inline_svg/transform_pipeline/transformations/aria_hidden_attribute.rb +9 -0
  22. data/lib/inline_svg/transform_pipeline/transformations/class_attribute.rb +5 -6
  23. data/lib/inline_svg/transform_pipeline/transformations/data_attributes.rb +4 -5
  24. data/lib/inline_svg/transform_pipeline/transformations/description.rb +7 -6
  25. data/lib/inline_svg/transform_pipeline/transformations/height.rb +3 -4
  26. data/lib/inline_svg/transform_pipeline/transformations/id_attribute.rb +3 -4
  27. data/lib/inline_svg/transform_pipeline/transformations/no_comment.rb +4 -4
  28. data/lib/inline_svg/transform_pipeline/transformations/preserve_aspect_ratio.rb +3 -4
  29. data/lib/inline_svg/transform_pipeline/transformations/size.rb +4 -5
  30. data/lib/inline_svg/transform_pipeline/transformations/style_attribute.rb +11 -0
  31. data/lib/inline_svg/transform_pipeline/transformations/title.rb +7 -6
  32. data/lib/inline_svg/transform_pipeline/transformations/transformation.rb +13 -0
  33. data/lib/inline_svg/transform_pipeline/transformations/view_box.rb +9 -0
  34. data/lib/inline_svg/transform_pipeline/transformations/width.rb +3 -4
  35. data/lib/inline_svg/transform_pipeline/transformations.rb +11 -2
  36. data/lib/inline_svg/transform_pipeline.rb +1 -1
  37. data/lib/inline_svg/version.rb +1 -1
  38. data/lib/inline_svg/webpack_asset_finder.rb +60 -0
  39. data/lib/inline_svg.rb +46 -9
  40. data/spec/cached_asset_file_spec.rb +73 -0
  41. data/spec/files/static_assets/assets0/known-document-two.svg +1 -0
  42. data/spec/files/static_assets/assets0/known-document.svg +1 -0
  43. data/spec/files/static_assets/assets0/some-document.svg +1 -0
  44. data/spec/files/static_assets/assets1/known-document.svg +1 -0
  45. data/spec/files/static_assets/assets1/other-document.svg +3 -0
  46. data/spec/files/static_assets/assets1/some-file.txt +1 -0
  47. data/spec/finds_asset_paths_spec.rb +45 -0
  48. data/spec/helpers/inline_svg_spec.rb +117 -51
  49. data/spec/id_generator_spec.rb +5 -3
  50. data/spec/inline_svg_spec.rb +48 -0
  51. data/spec/propshaft_asset_finder_spec.rb +23 -0
  52. data/spec/static_asset_finder_spec.rb +25 -0
  53. data/spec/transformation_pipeline/transformations/aria_attributes_spec.rb +6 -6
  54. data/spec/transformation_pipeline/transformations/aria_hidden_attribute_spec.rb +12 -0
  55. data/spec/transformation_pipeline/transformations/height_spec.rb +9 -0
  56. data/spec/transformation_pipeline/transformations/style_attribute_spec.rb +26 -0
  57. data/spec/transformation_pipeline/transformations/title_spec.rb +9 -0
  58. data/spec/transformation_pipeline/transformations/transformation_spec.rb +39 -0
  59. data/spec/transformation_pipeline/transformations/view_box_spec.rb +13 -0
  60. data/spec/transformation_pipeline/transformations_spec.rb +7 -1
  61. data/spec/webpack_asset_finder_spec.rb +23 -0
  62. metadata +62 -10
  63. data/circle.yml +0 -3
@@ -0,0 +1 @@
1
+ <svg>interesting content</svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3.273 7.151l-2.917-2.916c-.23.665-.356 1.361-.356 2.057 0 1.608.624 3.216 1.851 4.442 1.35 1.351 3.163 1.957 4.928 1.821.933-.072 1.851.268 2.513.93l9.646 9.646c.58.579 1.338.869 2.097.869 1.636 0 2.965-1.326 2.965-2.965 0-.759-.29-1.518-.868-2.097l-9.647-9.646c-.661-.662-1.002-1.581-.93-2.514.136-1.766-.47-3.578-1.821-4.928-.372-.372-.778-.686-1.209-.945l-6.252 6.246zm18.727 13.849c0 .552-.448 1-1 1s-1-.448-1-1 .448-1 1-1 1 .447 1 1zm-12.153-13.396l-3.061 3.061-2.566-2.567 3.062-3.061 2.565 2.567zm-.933.096l-.762.761-1.705-1.705.762-.762 1.705 1.706zm-2.991-.42l-.761.762 1.706 1.705.762-.762-1.707-1.705zm2.484-6.903l-2.893 2.893-2.412-2.412c.953-.556 2.044-.858 3.165-.858.707 0 1.425.12 2.128.373l.012.004z"/></svg>
@@ -0,0 +1 @@
1
+ <svg>Another known document</svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg>Other document</svg>
@@ -0,0 +1 @@
1
+ Some file contents.
@@ -45,4 +45,49 @@ describe InlineSvg::FindsAssetPaths do
45
45
  expect(InlineSvg::FindsAssetPaths.by_filename('some-file')).to be_nil
46
46
  end
47
47
  end
48
+
49
+ context "when propshaft finder returns an object which supports only the pathname method" do
50
+ it "returns fully qualified file paths from Propshaft" do
51
+ propshaft = double('PropshaftDouble')
52
+
53
+ expect(propshaft).to receive(:find_asset).with('some-file').
54
+ and_return(double(pathname: Pathname('/full/path/to/some-file')))
55
+
56
+ InlineSvg.configure do |config|
57
+ config.asset_finder = propshaft
58
+ end
59
+
60
+ expect(InlineSvg::FindsAssetPaths.by_filename('some-file')).to eq Pathname('/full/path/to/some-file')
61
+ end
62
+ end
63
+
64
+ context "when webpack finder returns an object with a relative asset path" do
65
+ it "returns the fully qualified file path" do
66
+ webpacker = double('WebpackerDouble')
67
+
68
+ expect(webpacker).to receive(:find_asset).with('some-file').
69
+ and_return(double(filename: Pathname('/full/path/to/some-file')))
70
+
71
+ InlineSvg.configure do |config|
72
+ config.asset_finder = webpacker
73
+ end
74
+
75
+ expect(InlineSvg::FindsAssetPaths.by_filename('some-file')).to eq Pathname('/full/path/to/some-file')
76
+ end
77
+ end
78
+
79
+ context "when webpack finder returns an object with an absolute http asset path" do
80
+ it "returns the fully qualified file path" do
81
+ webpacker = double('WebpackerDouble')
82
+
83
+ expect(webpacker).to receive(:find_asset).with('some-file').
84
+ and_return(double(filename: Pathname('https://my-fancy-domain.test/full/path/to/some-file')))
85
+
86
+ InlineSvg.configure do |config|
87
+ config.asset_finder = webpacker
88
+ end
89
+
90
+ expect(InlineSvg::FindsAssetPaths.by_filename('some-file')).to eq Pathname('https://my-fancy-domain.test/full/path/to/some-file')
91
+ end
92
+ end
48
93
  end
@@ -13,90 +13,138 @@ describe InlineSvg::ActionView::Helpers do
13
13
 
14
14
  let(:helper) { ( Class.new { include InlineSvg::ActionView::Helpers } ).new }
15
15
 
16
- describe "#inline_svg" do
17
-
16
+ shared_examples "inline_svg helper" do |helper_method:|
17
+
18
18
  context "when passed the name of an SVG that does not exist" do
19
+ after(:each) do
20
+ InlineSvg.reset_configuration!
21
+ end
22
+
23
+ context "and configured to raise" do
24
+ it "raises an exception" do
25
+ InlineSvg.configure do |config|
26
+ config.raise_on_file_not_found = true
27
+ end
28
+
29
+ allow(InlineSvg::AssetFile).to receive(:named).
30
+ with('some-missing-file.svg').
31
+ and_raise(InlineSvg::AssetFile::FileNotFound.new)
32
+
33
+ expect {
34
+ helper.send(helper_method, 'some-missing-file.svg')
35
+ }.to raise_error(InlineSvg::AssetFile::FileNotFound)
36
+ end
37
+ end
38
+
19
39
  it "returns an empty, html safe, SVG document as a placeholder" do
20
40
  allow(InlineSvg::AssetFile).to receive(:named).
21
41
  with('some-missing-file.svg').
22
42
  and_raise(InlineSvg::AssetFile::FileNotFound.new)
23
43
 
24
- output = helper.inline_svg('some-missing-file.svg')
44
+ output = helper.send(helper_method, 'some-missing-file.svg')
25
45
  expect(output).to eq "<svg><!-- SVG file not found: 'some-missing-file.svg' --></svg>"
26
46
  expect(output).to be_html_safe
27
47
  end
28
48
 
49
+ it "escapes malicious input" do
50
+ malicious = "--></svg><script>alert(1)</script><svg>.svg"
51
+ allow(InlineSvg::AssetFile).to receive(:named).
52
+ with(malicious).
53
+ and_raise(InlineSvg::AssetFile::FileNotFound.new)
54
+
55
+ output = helper.send(helper_method, malicious)
56
+ expect(output).to eq "<svg><!-- SVG file not found: '--&gt;&lt;/svg&gt;&lt;script&gt;alert(1)&lt;/script&gt;&lt;svg&gt;.svg' --></svg>"
57
+ expect(output).to be_html_safe
58
+ end
59
+
29
60
  it "gives a helpful hint when no .svg extension is provided in the filename" do
30
61
  allow(InlineSvg::AssetFile).to receive(:named).
31
62
  with('missing-file-with-no-extension').
32
63
  and_raise(InlineSvg::AssetFile::FileNotFound.new)
33
64
 
34
- output = helper.inline_svg('missing-file-with-no-extension')
65
+ output = helper.send(helper_method, 'missing-file-with-no-extension')
35
66
  expect(output).to eq "<svg><!-- SVG file not found: 'missing-file-with-no-extension' (Try adding .svg to your filename) --></svg>"
36
67
  end
68
+
69
+ it "allows the CSS class on the empty SVG document to be changed" do
70
+ InlineSvg.configure do |config|
71
+ config.svg_not_found_css_class = 'missing-svg'
72
+ end
73
+
74
+ allow(InlineSvg::AssetFile).to receive(:named).
75
+ with('some-other-missing-file.svg').
76
+ and_raise(InlineSvg::AssetFile::FileNotFound.new)
77
+
78
+ output = helper.send(helper_method, 'some-other-missing-file.svg')
79
+ expect(output).to eq "<svg class='missing-svg'><!-- SVG file not found: 'some-other-missing-file.svg' --></svg>"
80
+ expect(output).to be_html_safe
81
+ end
82
+
83
+ context "and a fallback that does exist" do
84
+ it "displays the fallback" do
85
+ allow(InlineSvg::AssetFile).to receive(:named).
86
+ with('missing.svg').
87
+ and_raise(InlineSvg::AssetFile::FileNotFound.new)
88
+
89
+ fallback_file = '<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><!-- This is a comment --></svg>'
90
+ allow(InlineSvg::AssetFile).to receive(:named).with('fallback.svg').and_return(fallback_file)
91
+ expect(helper.send(helper_method, 'missing.svg', fallback: 'fallback.svg')).to eq fallback_file
92
+ end
93
+ end
37
94
  end
38
95
 
39
96
  context "when passed an existing SVG file" do
40
97
 
41
98
  context "and no options" do
42
99
  it "returns a html safe version of the file's contents" do
43
- example_file = <<-SVG
44
- <svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><!-- This is a comment --></svg>
45
- SVG
100
+ example_file = '<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><!-- This is a comment --></svg>'
46
101
  allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(example_file)
47
- expect(helper.inline_svg('some-file')).to eq example_file
102
+ expect(helper.send(helper_method, 'some-file')).to eq example_file
48
103
  end
49
104
  end
50
105
 
51
106
  context "and the 'title' option" do
52
107
  it "adds the title node to the SVG output" do
53
- input_svg = <<-SVG
54
- <svg xmlns="http://www.w3.org/2000/svg" role="presentation" xml:lang="en"></svg>
55
- SVG
56
- expected_output = <<-SVG
57
- <svg xmlns="http://www.w3.org/2000/svg" role="presentation" xml:lang="en"><title>A title</title></svg>
58
- SVG
108
+ input_svg = '<svg xmlns="http://www.w3.org/2000/svg" role="presentation" xml:lang="en"></svg>'
109
+ expected_output = '<svg xmlns="http://www.w3.org/2000/svg" role="presentation" xml:lang="en"><title>A title</title></svg>'
59
110
  allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
60
- expect(helper.inline_svg('some-file', title: 'A title')).to eq expected_output
111
+ expect(helper.send(helper_method, 'some-file', title: 'A title')).to eq expected_output
61
112
  end
62
113
  end
63
114
 
64
115
  context "and the 'desc' option" do
65
116
  it "adds the description node to the SVG output" do
66
- input_svg = <<-SVG
67
- <svg xmlns="http://www.w3.org/2000/svg" role="presentation" xml:lang="en"></svg>
68
- SVG
69
- expected_output = <<-SVG
70
- <svg xmlns="http://www.w3.org/2000/svg" role="presentation" xml:lang="en"><desc>A description</desc></svg>
71
- SVG
117
+ input_svg = '<svg xmlns="http://www.w3.org/2000/svg" role="presentation" xml:lang="en"></svg>'
118
+ expected_output = '<svg xmlns="http://www.w3.org/2000/svg" role="presentation" xml:lang="en"><desc>A description</desc></svg>'
72
119
  allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
73
- expect(helper.inline_svg('some-file', desc: 'A description')).to eq expected_output
120
+ expect(helper.send(helper_method, 'some-file', desc: 'A description')).to eq expected_output
74
121
  end
75
122
  end
76
123
 
77
124
  context "and the 'nocomment' option" do
78
125
  it "strips comments and other unknown/unsafe nodes from the output" do
79
- input_svg = <<-SVG
80
- <svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><!-- This is a comment --></svg>
81
- SVG
82
- expected_output = <<-SVG
83
- <svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"></svg>
84
- SVG
126
+ input_svg = '<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><!-- This is a comment --></svg>'
127
+ expected_output = '<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"></svg>'
85
128
  allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
86
- expect(helper.inline_svg('some-file', nocomment: true)).to eq expected_output
129
+ expect(helper.send(helper_method, 'some-file', nocomment: true)).to eq expected_output
130
+ end
131
+ end
132
+
133
+ context "and the 'aria_hidden' option" do
134
+ it "sets 'aria-hidden=true' in the output" do
135
+ input_svg = '<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"></svg>'
136
+ expected_output = '<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en" aria-hidden="true"></svg>'
137
+ allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
138
+ expect(helper.send(helper_method, 'some-file', aria_hidden: true)).to eq expected_output
87
139
  end
88
140
  end
89
141
 
90
142
  context "and all options" do
91
143
  it "applies all expected transformations to the output" do
92
- input_svg = <<-SVG
93
- <svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><!-- This is a comment --></svg>
94
- SVG
95
- expected_output = <<-SVG
96
- <svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><title>A title</title><desc>A description</desc></svg>
97
- SVG
144
+ input_svg = '<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><!-- This is a comment --></svg>'
145
+ expected_output = '<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"><title>A title</title><desc>A description</desc></svg>'
98
146
  allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
99
- expect(helper.inline_svg('some-file', title: 'A title', desc: 'A description', nocomment: true)).to eq expected_output
147
+ expect(helper.send(helper_method, 'some-file', title: 'A title', desc: 'A description', nocomment: true)).to eq expected_output
100
148
  end
101
149
  end
102
150
 
@@ -112,14 +160,10 @@ SVG
112
160
  end
113
161
 
114
162
  it "applies custm transformations to the output" do
115
- input_svg = <<-SVG
116
- <svg></svg>
117
- SVG
118
- expected_output = <<-SVG
119
- <svg custom="some value"></svg>
120
- SVG
163
+ input_svg = '<svg></svg>'
164
+ expected_output = '<svg custom="some value"></svg>'
121
165
  allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
122
- expect(helper.inline_svg('some-file', custom: 'some value')).to eq expected_output
166
+ expect(helper.send(helper_method, 'some-file', custom: 'some value')).to eq expected_output
123
167
  end
124
168
  end
125
169
 
@@ -140,7 +184,7 @@ SVG
140
184
 
141
185
  allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
142
186
 
143
- expect(helper.inline_svg('some-file')).to eq "<svg custom=\"default value\"></svg>\n"
187
+ expect(helper.send(helper_method, 'some-file')).to eq "<svg custom=\"default value\"></svg>"
144
188
  end
145
189
  end
146
190
 
@@ -150,7 +194,7 @@ SVG
150
194
 
151
195
  allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
152
196
 
153
- expect(helper.inline_svg('some-file', custom: 'some value')).to eq "<svg custom=\"some value\"></svg>\n"
197
+ expect(helper.send(helper_method, 'some-file', custom: 'some value')).to eq "<svg custom=\"some value\"></svg>"
154
198
  end
155
199
  end
156
200
  end
@@ -162,13 +206,13 @@ SVG
162
206
  expect(InlineSvg::IOResource).to receive(:===).with(argument).and_return(true)
163
207
  expect(InlineSvg::IOResource).to receive(:read).with(argument)
164
208
  expect(InlineSvg::AssetFile).to_not receive(:named)
165
- helper.inline_svg(argument)
209
+ helper.send(helper_method, argument)
166
210
  end
167
211
  it 'accept filename' do
168
212
  expect(InlineSvg::IOResource).to receive(:===).with(argument).and_return(false)
169
213
  expect(InlineSvg::IOResource).to_not receive(:read)
170
214
  expect(InlineSvg::AssetFile).to receive(:named).with(argument)
171
- helper.inline_svg(argument)
215
+ helper.send(helper_method, argument)
172
216
  end
173
217
  end
174
218
  context 'when passed IO object argument' do
@@ -178,17 +222,39 @@ SVG
178
222
  it 'return valid svg' do
179
223
  expect(InlineSvg::IOResource).to receive(:===).with(io_object).and_return(true)
180
224
  expect(InlineSvg::IOResource).to receive(:read).with(io_object).and_return("<svg><!-- Test IO --></svg>")
181
- output = helper.inline_svg(io_object)
182
- expect(output).to eq "<svg><!-- Test IO --></svg>\n"
225
+ output = helper.send(helper_method, io_object)
226
+ expect(output).to eq "<svg><!-- Test IO --></svg>"
183
227
  expect(output).to be_html_safe
184
228
  end
185
229
 
186
230
  it 'return valid svg for file' do
187
- output = helper.inline_svg(File.new(file_path))
188
- expect(output).to eq "<svg xmlns=\"http://www.w3.org/2000/svg\" xml:lang=\"en\" role=\"presentation\"><!-- This is a test comment --></svg>\n"
231
+ output = helper.send(helper_method, File.new(file_path))
232
+ expect(output).to eq "<svg xmlns=\"http://www.w3.org/2000/svg\" xml:lang=\"en\" role=\"presentation\"><!-- This is a test comment --></svg>"
189
233
  expect(output).to be_html_safe
190
234
  end
191
235
 
192
236
  end
237
+
238
+ context 'default output' do
239
+ it "returns an SVG tag without any pre or post whitespace characters" do
240
+ input_svg = '<svg></svg>'
241
+
242
+ allow(InlineSvg::AssetFile).to receive(:named).with('some-file').and_return(input_svg)
243
+
244
+ expect(helper.send(helper_method, 'some-file')).to eq "<svg></svg>"
245
+ end
246
+ end
247
+ end
248
+
249
+ describe '#inline_svg' do
250
+ it_behaves_like "inline_svg helper", helper_method: :inline_svg
251
+ end
252
+
253
+ describe '#inline_svg_tag' do
254
+ it_behaves_like "inline_svg helper", helper_method: :inline_svg_tag
255
+ end
256
+
257
+ describe '#inline_svg_tag' do
258
+ it_behaves_like "inline_svg helper", helper_method: :inline_svg_pack_tag
193
259
  end
194
260
  end
@@ -1,8 +1,10 @@
1
1
  require_relative '../lib/inline_svg/id_generator'
2
2
 
3
3
  describe InlineSvg::IdGenerator do
4
- it "generates a hexencoded ID based on a salt" do
5
- expect(InlineSvg::IdGenerator.generate("some-base", "some-salt")).
6
- to eq("ksiuuy1jduycacqpoj5smn2kyt9iv02")
4
+ it "generates a hexencoded ID based on a salt and a random value" do
5
+ randomizer = -> { "some-random-value" }
6
+
7
+ expect(InlineSvg::IdGenerator.generate("some-base", "some-salt", randomness: randomizer)).
8
+ to eq("at2c17mkqnvopy36iccxspura7wnreqf")
7
9
  end
8
10
  end
@@ -13,6 +13,10 @@ class MyInvalidCustomTransformInstance
13
13
  def self.create_with_value(value); end
14
14
  end
15
15
 
16
+ class MyCustomAssetFile
17
+ def self.named(filename); end
18
+ end
19
+
16
20
  describe InlineSvg do
17
21
  describe "configuration" do
18
22
  context "when a block is not given" do
@@ -42,6 +46,50 @@ describe InlineSvg do
42
46
  end
43
47
  end
44
48
 
49
+ context "configuring a custom asset file" do
50
+ it "falls back to the built-in asset file implementation by default" do
51
+ expect(InlineSvg.configuration.asset_file).to eq(InlineSvg::AssetFile)
52
+ end
53
+
54
+ it "adds a collaborator that meets the interface specification" do
55
+ InlineSvg.configure do |config|
56
+ config.asset_file = MyCustomAssetFile
57
+ end
58
+
59
+ expect(InlineSvg.configuration.asset_file).to eq MyCustomAssetFile
60
+ end
61
+
62
+ it "rejects a collaborator that does not conform to the interface spec" do
63
+ bad_asset_file = double("bad_asset_file")
64
+
65
+ expect do
66
+ InlineSvg.configure do |config|
67
+ config.asset_file = bad_asset_file
68
+ end
69
+ end.to raise_error(InlineSvg::Configuration::Invalid, /asset_file should implement the #named method/)
70
+ end
71
+
72
+ it "rejects a collaborator that implements the correct interface with the wrong arity" do
73
+ bad_asset_file = double("bad_asset_file", named: nil)
74
+
75
+ expect do
76
+ InlineSvg.configure do |config|
77
+ config.asset_file = bad_asset_file
78
+ end
79
+ end.to raise_error(InlineSvg::Configuration::Invalid, /asset_file should implement the #named method with arity 1/)
80
+ end
81
+ end
82
+
83
+ context "configuring the default svg-not-found class" do
84
+ it "sets the class name" do
85
+ InlineSvg.configure do |config|
86
+ config.svg_not_found_css_class = 'missing-svg'
87
+ end
88
+
89
+ expect(InlineSvg.configuration.svg_not_found_css_class).to eq 'missing-svg'
90
+ end
91
+ end
92
+
45
93
  context "configuring custom transformation" do
46
94
  it "allows a custom transformation to be added" do
47
95
  InlineSvg.configure do |config|
@@ -0,0 +1,23 @@
1
+ require_relative '../lib/inline_svg'
2
+
3
+ describe InlineSvg::PropshaftAssetFinder do
4
+ context "when the file is not found" do
5
+ it "returns nil" do
6
+ stub_const('Rails', double('Rails').as_null_object)
7
+ expect(::Rails.application.assets.load_path).to receive(:find).with('some-file').and_return(nil)
8
+
9
+ expect(InlineSvg::PropshaftAssetFinder.find_asset('some-file').pathname).to be_nil
10
+ end
11
+ end
12
+
13
+ context "when the file is found" do
14
+ it "returns fully qualified file paths from Propshaft" do
15
+ stub_const('Rails', double('Rails').as_null_object)
16
+ asset = double('Asset')
17
+ expect(asset).to receive(:path).and_return(Pathname.new('/full/path/to/some-file'))
18
+ expect(::Rails.application.assets.load_path).to receive(:find).with('some-file').and_return(asset)
19
+
20
+ expect(InlineSvg::PropshaftAssetFinder.find_asset('some-file').pathname).to eq Pathname('/full/path/to/some-file')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../lib/inline_svg'
2
+
3
+ describe InlineSvg::StaticAssetFinder do
4
+ context "when the file is not found" do
5
+ it "returns nil" do
6
+ stub_const('Rails', double('Rails').as_null_object)
7
+ expect(::Rails.application.config.assets).to receive(:compile).and_return(true)
8
+
9
+ expect(described_class.find_asset('some-file').pathname).to be_nil
10
+ end
11
+ end
12
+
13
+ context "when the file is found" do
14
+ it "returns fully qualified file path from Sprockets" do
15
+ stub_const('Rails', double('Rails').as_null_object)
16
+ expect(::Rails.application.config.assets).to receive(:compile).and_return(true)
17
+ pathname = Pathname.new('/full/path/to/some-file')
18
+ asset = double('Asset')
19
+ expect(asset).to receive(:filename).and_return(pathname)
20
+ expect(::Rails.application.assets).to receive(:[]).with('some-file').and_return(asset)
21
+
22
+ expect(described_class.find_asset('some-file').pathname).to eq(pathname)
23
+ end
24
+ end
25
+ end
@@ -1,8 +1,8 @@
1
- require 'inline_svg/transform_pipeline'
1
+ require "inline_svg/transform_pipeline"
2
2
 
3
3
  describe InlineSvg::TransformPipeline::Transformations::AriaAttributes do
4
4
  it "adds a role attribute to the SVG document" do
5
- document = Nokogiri::XML::Document.parse('<svg>Some document</svg>')
5
+ document = Nokogiri::XML::Document.parse("<svg>Some document</svg>")
6
6
  transformation = InlineSvg::TransformPipeline::Transformations::AriaAttributes.create_with_value({})
7
7
 
8
8
  expect(transformation.transform(document).to_html).to eq(
@@ -12,7 +12,7 @@ describe InlineSvg::TransformPipeline::Transformations::AriaAttributes do
12
12
 
13
13
  context "aria-labelledby attribute" do
14
14
  it "adds 'title' when a title element is present" do
15
- document = Nokogiri::XML::Document.parse('<svg><title>Some title</title>Some document</svg>')
15
+ document = Nokogiri::XML::Document.parse("<svg><title>Some title</title>Some document</svg>")
16
16
  transformation = InlineSvg::TransformPipeline::Transformations::AriaAttributes.create_with_value(true)
17
17
 
18
18
  expect(InlineSvg::IdGenerator).to receive(:generate).with("title", "Some title").
@@ -24,7 +24,7 @@ describe InlineSvg::TransformPipeline::Transformations::AriaAttributes do
24
24
  end
25
25
 
26
26
  it "adds 'desc' when a description element is present" do
27
- document = Nokogiri::XML::Document.parse('<svg><desc>Some description</desc>Some document</svg>')
27
+ document = Nokogiri::XML::Document.parse("<svg><desc>Some description</desc>Some document</svg>")
28
28
  transformation = InlineSvg::TransformPipeline::Transformations::AriaAttributes.create_with_value(true)
29
29
 
30
30
  expect(InlineSvg::IdGenerator).to receive(:generate).with("desc", "Some description").
@@ -36,7 +36,7 @@ describe InlineSvg::TransformPipeline::Transformations::AriaAttributes do
36
36
  end
37
37
 
38
38
  it "adds both 'desc' and 'title' when title and description elements are present" do
39
- document = Nokogiri::XML::Document.parse('<svg><title>Some title</title><desc>Some description</desc>Some document</svg>')
39
+ document = Nokogiri::XML::Document.parse("<svg><title>Some title</title><desc>Some description</desc>Some document</svg>")
40
40
  transformation = InlineSvg::TransformPipeline::Transformations::AriaAttributes.create_with_value(true)
41
41
 
42
42
  expect(InlineSvg::IdGenerator).to receive(:generate).with("title", "Some title").
@@ -50,7 +50,7 @@ describe InlineSvg::TransformPipeline::Transformations::AriaAttributes do
50
50
  end
51
51
 
52
52
  it "uses existing IDs when they exist" do
53
- document = Nokogiri::XML::Document.parse('<svg><title id="my-title">Some title</title><desc id="my-desc">Some description</desc>Some document</svg>')
53
+ document = Nokogiri::XML::Document.parse("<svg><title id='my-title'>Some title</title><desc id='my-desc'>Some description</desc>Some document</svg>")
54
54
  transformation = InlineSvg::TransformPipeline::Transformations::AriaAttributes.create_with_value(true)
55
55
 
56
56
  expect(InlineSvg::IdGenerator).to receive(:generate).with("my-title", "Some title").
@@ -0,0 +1,12 @@
1
+ require 'inline_svg/transform_pipeline'
2
+
3
+ describe InlineSvg::TransformPipeline::Transformations::AriaHiddenAttribute do
4
+ it "adds an aria-hidden='true' attribute to a SVG document" do
5
+ document = Nokogiri::XML::Document.parse('<svg>Some document</svg>')
6
+ transformation = InlineSvg::TransformPipeline::Transformations::AriaHiddenAttribute.create_with_value(true)
7
+
8
+ expect(transformation.transform(document).to_html).to eq(
9
+ "<svg aria-hidden=\"true\">Some document</svg>\n"
10
+ )
11
+ end
12
+ end
@@ -9,4 +9,13 @@ describe InlineSvg::TransformPipeline::Transformations::Height do
9
9
  "<svg height=\"5%\">Some document</svg>\n"
10
10
  )
11
11
  end
12
+
13
+ it "handles documents without SVG root elements" do
14
+ document = Nokogiri::XML::Document.parse("<foo>bar</foo><svg>Some document</svg>")
15
+ transformation = InlineSvg::TransformPipeline::Transformations::Height.create_with_value("5%")
16
+
17
+ expect(transformation.transform(document).to_html).to eq(
18
+ "<foo>bar</foo>\n"
19
+ )
20
+ end
12
21
  end
@@ -0,0 +1,26 @@
1
+ require "inline_svg/transform_pipeline"
2
+
3
+ describe InlineSvg::TransformPipeline::Transformations::ClassAttribute do
4
+ it "adds a style attribute to a SVG document" do
5
+ document = Nokogiri::XML::Document.parse('<svg>Some document</svg>')
6
+ transformation =
7
+ InlineSvg::TransformPipeline::Transformations::StyleAttribute
8
+ .create_with_value("padding: 10px")
9
+
10
+ expect(transformation.transform(document).to_html).to eq(
11
+ "<svg style=\"padding: 10px\">Some document</svg>\n"
12
+ )
13
+ end
14
+
15
+ it "preserves existing style attributes on a SVG document" do
16
+ xml = '<svg style="fill: red">Some document</svg>'
17
+ document = Nokogiri::XML::Document.parse(xml)
18
+ transformation =
19
+ InlineSvg::TransformPipeline::Transformations::StyleAttribute
20
+ .create_with_value("padding: 10px")
21
+
22
+ expect(transformation.transform(document).to_html).to eq(
23
+ "<svg style=\"fill: red;padding: 10px\">Some document</svg>\n"
24
+ )
25
+ end
26
+ end
@@ -27,4 +27,13 @@ describe InlineSvg::TransformPipeline::Transformations::Title do
27
27
  "<svg><title>Some Title</title></svg>\n"
28
28
  )
29
29
  end
30
+
31
+ it "handles non-ASCII characters" do
32
+ document = Nokogiri::XML::Document.parse('<svg>Some document</svg>')
33
+ transformation = InlineSvg::TransformPipeline::Transformations::Title.create_with_value("åäö")
34
+
35
+ expect(transformation.transform(document).to_html).to eq(
36
+ "<svg><title>åäö</title>Some document</svg>\n"
37
+ )
38
+ end
30
39
  end
@@ -0,0 +1,39 @@
1
+ require 'inline_svg'
2
+ require 'inline_svg/transform_pipeline'
3
+
4
+ describe InlineSvg::TransformPipeline::Transformations::Transformation do
5
+ context "#with_svg" do
6
+ it "returns a Nokogiri::XML::Document representing the parsed document fragment" do
7
+ document = Nokogiri::XML::Document.parse("<svg>Some document</svg>")
8
+
9
+ transformation = InlineSvg::TransformPipeline::Transformations::Transformation.new(:irrelevant)
10
+ expect(transformation.with_svg(document).to_html).to eq(
11
+ "<svg>Some document</svg>\n"
12
+ )
13
+ end
14
+
15
+ it "yields to the block when the document contains an SVG element" do
16
+ document = Nokogiri::XML::Document.parse("<svg>Some document</svg>")
17
+ svg = document.at_css("svg")
18
+
19
+ transformation = InlineSvg::TransformPipeline::Transformations::Transformation.new(:irrelevant)
20
+
21
+ returned_document = nil
22
+ expect do |b|
23
+ returned_document = transformation.with_svg(document, &b)
24
+ end.to yield_control
25
+
26
+ expect(returned_document.to_s).to match(/<svg>Some document<\/svg>/)
27
+ end
28
+
29
+ it "does not yield if the document does not contain an SVG element at the root" do
30
+ document = Nokogiri::XML::Document.parse("<foo>bar</foo><svg>Some document</svg>")
31
+
32
+ transformation = InlineSvg::TransformPipeline::Transformations::Transformation.new(:irrelevant)
33
+
34
+ expect do |b|
35
+ transformation.with_svg(document, &b)
36
+ end.not_to yield_control
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ require 'inline_svg/transform_pipeline'
2
+
3
+ describe InlineSvg::TransformPipeline::Transformations::ViewBox do
4
+ it "adds viewBox attribute to a SVG document" do
5
+ document = Nokogiri::XML::Document.parse('<svg>Some document</svg>')
6
+ transformation =
7
+ InlineSvg::TransformPipeline::Transformations::ViewBox
8
+ .create_with_value("0 0 100 100")
9
+ expect(transformation.transform(document).to_html).to eq(
10
+ "<svg viewBox=\"0 0 100 100\">Some document</svg>\n"
11
+ )
12
+ end
13
+ end