hanami 2.1.0.beta1 → 2.1.0.beta2.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -4
  3. data/README.md +1 -1
  4. data/lib/hanami/app.rb +5 -0
  5. data/lib/hanami/config/actions.rb +4 -7
  6. data/lib/hanami/config/assets.rb +84 -0
  7. data/lib/hanami/config/null_config.rb +3 -0
  8. data/lib/hanami/config.rb +17 -5
  9. data/lib/hanami/extensions/action.rb +4 -2
  10. data/lib/hanami/extensions/view/standard_helpers.rb +4 -0
  11. data/lib/hanami/helpers/assets_helper.rb +772 -0
  12. data/lib/hanami/middleware/assets.rb +21 -0
  13. data/lib/hanami/middleware/render_errors.rb +4 -7
  14. data/lib/hanami/providers/assets.rb +44 -0
  15. data/lib/hanami/rake_tasks.rb +19 -18
  16. data/lib/hanami/settings.rb +1 -1
  17. data/lib/hanami/slice.rb +25 -4
  18. data/lib/hanami/version.rb +1 -1
  19. data/lib/hanami.rb +2 -2
  20. data/spec/integration/assets/assets_spec.rb +101 -0
  21. data/spec/integration/assets/serve_static_assets_spec.rb +152 -0
  22. data/spec/integration/logging/exception_logging_spec.rb +115 -0
  23. data/spec/integration/logging/notifications_spec.rb +68 -0
  24. data/spec/integration/logging/request_logging_spec.rb +128 -0
  25. data/spec/integration/rack_app/middleware_spec.rb +4 -4
  26. data/spec/integration/rack_app/rack_app_spec.rb +0 -221
  27. data/spec/integration/rake_tasks_spec.rb +107 -0
  28. data/spec/integration/view/context/assets_spec.rb +3 -9
  29. data/spec/integration/web/render_detailed_errors_spec.rb +17 -0
  30. data/spec/integration/web/render_errors_spec.rb +6 -4
  31. data/spec/support/app_integration.rb +46 -2
  32. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +24 -36
  33. data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +4 -3
  34. data/spec/unit/hanami/config/actions/default_values_spec.rb +3 -2
  35. data/spec/unit/hanami/env_spec.rb +11 -25
  36. data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +109 -0
  37. data/spec/unit/hanami/helpers/assets_helper/audio_spec.rb +136 -0
  38. data/spec/unit/hanami/helpers/assets_helper/favicon_spec.rb +91 -0
  39. data/spec/unit/hanami/helpers/assets_helper/image_spec.rb +96 -0
  40. data/spec/unit/hanami/helpers/assets_helper/javascript_spec.rb +147 -0
  41. data/spec/unit/hanami/helpers/assets_helper/stylesheet_spec.rb +130 -0
  42. data/spec/unit/hanami/helpers/assets_helper/video_spec.rb +136 -0
  43. data/spec/unit/hanami/version_spec.rb +1 -1
  44. metadata +32 -4
  45. data/lib/hanami/assets/app_config.rb +0 -61
  46. data/lib/hanami/assets/config.rb +0 -53
@@ -3,43 +3,31 @@
3
3
  require "hanami/config/actions"
4
4
 
5
5
  RSpec.describe Hanami::Config::Actions, "#content_security_policy" do
6
- let(:config) { described_class.new }
6
+ let(:app_config) { Hanami::Config.new(app_name: "MyApp::App", env: :development) }
7
+ let(:config) { app_config.actions }
7
8
  subject(:content_security_policy) { config.content_security_policy }
8
9
 
9
10
  context "no CSP config specified" do
10
- context "without assets_server_url" do
11
-
12
- it "has defaults" do
13
- expect(content_security_policy[:base_uri]).to eq("'self'")
14
-
15
- expected = [
16
- %(base-uri 'self'),
17
- %(child-src 'self'),
18
- %(connect-src 'self'),
19
- %(default-src 'none'),
20
- %(font-src 'self'),
21
- %(form-action 'self'),
22
- %(frame-ancestors 'self'),
23
- %(frame-src 'self'),
24
- %(img-src 'self' https: data:),
25
- %(media-src 'self'),
26
- %(object-src 'none'),
27
- %(script-src 'self'),
28
- %(style-src 'self' 'unsafe-inline' https:)
29
- ].join(";")
30
-
31
- expect(content_security_policy.to_s).to eq(expected)
32
- end
33
- end
34
-
35
- context "with assets_server_url" do
36
- let(:config) { described_class.new(assets_server_url: assets_server_url) }
37
- let(:assets_server_url) { "http://localhost:8080" }
38
-
39
- it "includes server url" do
40
- expect(content_security_policy[:script_src]).to eq("'self' #{assets_server_url}")
41
- expect(content_security_policy[:style_src]).to eq("'self' 'unsafe-inline' https: #{assets_server_url}")
42
- end
11
+ it "has defaults" do
12
+ expect(content_security_policy[:base_uri]).to eq("'self'")
13
+
14
+ expected = [
15
+ %(base-uri 'self'),
16
+ %(child-src 'self'),
17
+ %(connect-src 'self'),
18
+ %(default-src 'none'),
19
+ %(font-src 'self'),
20
+ %(form-action 'self'),
21
+ %(frame-ancestors 'self'),
22
+ %(frame-src 'self'),
23
+ %(img-src 'self' https: data:),
24
+ %(media-src 'self'),
25
+ %(object-src 'none'),
26
+ %(script-src 'self'),
27
+ %(style-src 'self' 'unsafe-inline' https:)
28
+ ].join(";")
29
+
30
+ expect(content_security_policy.to_s).to eq(expected)
43
31
  end
44
32
  end
45
33
 
@@ -84,7 +72,7 @@ RSpec.describe Hanami::Config::Actions, "#content_security_policy" do
84
72
 
85
73
  context "with CSP enabled" do
86
74
  it "sets default header" do
87
- config.finalize!
75
+ app_config.finalize!
88
76
 
89
77
  expect(config.default_headers.fetch("Content-Security-Policy")).to eq(content_security_policy.to_s)
90
78
  end
@@ -93,7 +81,7 @@ RSpec.describe Hanami::Config::Actions, "#content_security_policy" do
93
81
  context "with CSP disabled" do
94
82
  it "doesn't set default header" do
95
83
  config.content_security_policy = false
96
- config.finalize!
84
+ app_config.finalize!
97
85
 
98
86
  expect(config.default_headers.key?("Content-Security-Policy")).to be(false)
99
87
  end
@@ -3,7 +3,8 @@
3
3
  require "hanami/config/actions"
4
4
 
5
5
  RSpec.describe Hanami::Config::Actions, "#csrf_protection" do
6
- let(:config) { described_class.new }
6
+ let(:app_config) { Hanami::Config.new(app_name: "MyApp::App", env: :development) }
7
+ let(:config) { app_config.actions }
7
8
  subject(:value) { config.csrf_protection }
8
9
 
9
10
  context "non-finalized config" do
@@ -26,7 +27,7 @@ RSpec.describe Hanami::Config::Actions, "#csrf_protection" do
26
27
  context "sessions enabled" do
27
28
  before do
28
29
  config.sessions = :cookie, {secret: "abc"}
29
- config.finalize!
30
+ app_config.finalize!
30
31
  end
31
32
 
32
33
  it "is true" do
@@ -46,7 +47,7 @@ RSpec.describe Hanami::Config::Actions, "#csrf_protection" do
46
47
 
47
48
  context "sessions not enabled" do
48
49
  before do
49
- config.finalize!
50
+ app_config.finalize!
50
51
  end
51
52
 
52
53
  it "is true" do
@@ -3,7 +3,8 @@
3
3
  require "hanami/config/actions"
4
4
 
5
5
  RSpec.describe Hanami::Config::Actions, "default values" do
6
- subject(:config) { described_class.new }
6
+ let(:app_config) { Hanami::Config.new(app_name: "MyApp::App", env: :development) }
7
+ subject(:config) { app_config.actions }
7
8
 
8
9
  describe "sessions" do
9
10
  specify { expect(config.sessions).not_to be_enabled }
@@ -28,7 +29,7 @@ RSpec.describe Hanami::Config::Actions, "default values" do
28
29
 
29
30
  describe "default_headers" do
30
31
  specify {
31
- config.finalize!
32
+ app_config.finalize!
32
33
 
33
34
  expect(config.default_headers).to eq(
34
35
  "X-Frame-Options" => "DENY",
@@ -1,54 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Hanami, ".env" do
4
- subject(:env) { described_class.env }
5
-
6
- before do
7
- @orig_env = ENV.to_h
8
- end
9
-
10
- after do
11
- ENV.replace(@orig_env)
12
- end
4
+ subject { described_class.env(e: env) }
13
5
 
14
6
  context "HANAMI_ENV in ENV" do
15
- before do
16
- ENV["HANAMI_ENV"] = "test"
17
- end
7
+ let(:env) { {"HANAMI_ENV" => "test"} }
18
8
 
19
9
  it "is the value of HANAMI_ENV" do
20
- is_expected.to eq :test
10
+ is_expected.to eq(:test)
21
11
  end
22
12
  end
23
13
 
24
14
  context "RACK_ENV in ENV" do
25
- before do
26
- ENV["RACK_ENV"] = "test"
27
- end
15
+ let(:env) { {"HANAMI_ENV" => "test"} }
28
16
 
29
17
  it "is the value of RACK_ENV" do
30
- is_expected.to eq :test
18
+ is_expected.to eq(:test)
31
19
  end
32
20
  end
33
21
 
34
22
  context "both HANAMI_ENV and RACK_ENV in ENV" do
35
- before do
36
- ENV["HANAMI_ENV"] = "test"
37
- ENV["RACK_ENV"] = "production"
23
+ let(:env) do
24
+ {"HANAMI_ENV" => "test",
25
+ "RACK_ENV" => "production"}
38
26
  end
39
27
 
40
28
  it "is the value of HANAMI_ENV" do
41
- is_expected.to eq :test
29
+ is_expected.to eq(:test)
42
30
  end
43
31
  end
44
32
 
45
33
  context "no ENV vars set" do
46
- before do
47
- ENV.delete("HANAMI_ENV")
48
- end
34
+ let(:env) { {} }
49
35
 
50
36
  it "defaults to \"development\"" do
51
- is_expected.to eq :development
37
+ is_expected.to eq(:development)
52
38
  end
53
39
  end
54
40
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Hanami::Helpers::AssetsHelper, "#asset_url", :app_integration do
4
+ subject(:obj) {
5
+ helpers = described_class
6
+ Class.new {
7
+ include helpers
8
+
9
+ attr_reader :_context
10
+
11
+ def initialize(context)
12
+ @_context = context
13
+ end
14
+ }.new(context)
15
+ }
16
+
17
+ def asset_url(...)
18
+ subject.asset_url(...)
19
+ end
20
+
21
+ let(:context) { TestApp::Views::Context.new }
22
+ let(:root) { make_tmp_directory }
23
+
24
+ before do
25
+ with_directory(root) do
26
+ write "config/app.rb", <<~RUBY
27
+ module TestApp
28
+ class App < Hanami::App
29
+ config.logger.stream = StringIO.new
30
+ end
31
+ end
32
+ RUBY
33
+
34
+ write "app/views/context.rb", <<~RUBY
35
+ # auto_register: false
36
+
37
+ require "hanami/view/context"
38
+
39
+ module TestApp
40
+ module Views
41
+ class Context < Hanami::View::Context
42
+ end
43
+ end
44
+ end
45
+ RUBY
46
+
47
+ write "app/assets/js/app.ts", <<~JS
48
+ import "../css/app.css";
49
+
50
+ console.log("Hello from index.ts");
51
+ JS
52
+
53
+ write "app/assets/css/app.css", <<~CSS
54
+ .btn {
55
+ background: #f00;
56
+ }
57
+ CSS
58
+
59
+ stub_assets("app.js")
60
+
61
+ require "hanami/setup"
62
+ before_prepare if respond_to?(:before_prepare)
63
+ require "hanami/prepare"
64
+ end
65
+ end
66
+
67
+ context "when configurated relative path only" do
68
+ context "without manifest" do
69
+ it "returns the relative URL to the asset" do
70
+ expect(asset_url("app.js")).to eq("/assets/app.js")
71
+ end
72
+
73
+ it "returns absolute URL if the argument is an absolute URL" do
74
+ result = asset_url("http://assets.hanamirb.org/assets/application.css")
75
+ expect(result).to eq("http://assets.hanamirb.org/assets/application.css")
76
+ end
77
+ end
78
+
79
+ context "with manifest" do
80
+ before { compile_assets! }
81
+
82
+ it "returns the relative URL to the asset" do
83
+ expect(asset_url("app.js")).to match(%r{/assets/app-[A-Z0-9]{8}\.js})
84
+ end
85
+ end
86
+ end
87
+
88
+ context "when configured with base url" do
89
+ let(:base_url) { "https://hanami.test" }
90
+
91
+ def before_prepare
92
+ Hanami.app.config.assets.base_url = base_url
93
+ end
94
+
95
+ context "without manifest" do
96
+ it "returns the absolute URL to the asset" do
97
+ expect(asset_url("app.js")).to eq("#{base_url}/assets/app.js")
98
+ end
99
+ end
100
+
101
+ context "with manifest" do
102
+ before { compile_assets! }
103
+
104
+ it "returns the relative path to the asset" do
105
+ expect(asset_url("app.js")).to match(%r{#{base_url}/assets/app-[A-Z0-9]{8}.js})
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Hanami::Helpers::AssetsHelper, "#audio", :app_integration do
4
+ subject(:obj) {
5
+ helpers = described_class
6
+ Class.new {
7
+ include helpers
8
+
9
+ attr_reader :_context
10
+
11
+ def initialize(context)
12
+ @_context = context
13
+ end
14
+ }.new(context)
15
+ }
16
+
17
+ def audio(...)
18
+ subject.audio(...)
19
+ end
20
+
21
+ let(:context) { TestApp::Views::Context.new }
22
+ let(:root) { make_tmp_directory }
23
+
24
+ before do
25
+ with_directory(root) do
26
+ write "config/app.rb", <<~RUBY
27
+ module TestApp
28
+ class App < Hanami::App
29
+ config.logger.stream = StringIO.new
30
+ end
31
+ end
32
+ RUBY
33
+
34
+ write "app/views/context.rb", <<~RUBY
35
+ # auto_register: false
36
+
37
+ require "hanami/view/context"
38
+
39
+ module TestApp
40
+ module Views
41
+ class Context < Hanami::View::Context
42
+ end
43
+ end
44
+ end
45
+ RUBY
46
+
47
+ stub_assets("song.ogg", "song.pt-BR.vtt")
48
+
49
+ require "hanami/setup"
50
+ before_prepare if respond_to?(:before_prepare)
51
+ require "hanami/prepare"
52
+ end
53
+ end
54
+
55
+ it "returns an instance of HtmlBuilder" do
56
+ actual = audio("song.ogg")
57
+ expect(actual).to be_instance_of(::Hanami::View::HTML::SafeString)
58
+ end
59
+
60
+ it "renders <audio> tag" do
61
+ actual = audio("song.ogg").to_s
62
+ expect(actual).to eq(%(<audio src="/assets/song.ogg"></audio>))
63
+ end
64
+
65
+ it "is aliased as #audio_tag" do
66
+ expect(subject.audio_tag("song.ogg")).to eq(audio("song.ogg"))
67
+ end
68
+
69
+ it "renders with html attributes" do
70
+ actual = audio("song.ogg", autoplay: true, controls: true).to_s
71
+ expect(actual).to eq(%(<audio autoplay="autoplay" controls="controls" src="/assets/song.ogg"></audio>))
72
+ end
73
+
74
+ it "renders with fallback content" do
75
+ actual = audio("song.ogg") do
76
+ "Your browser does not support the audio tag"
77
+ end.to_s
78
+
79
+ expect(actual).to eq(%(<audio src="/assets/song.ogg">Your browser does not support the audio tag</audio>))
80
+ end
81
+
82
+ it "renders with tracks" do
83
+ actual = audio("song.ogg") do
84
+ tag.track kind: "captions", src: subject.asset_url("song.pt-BR.vtt"), srclang: "pt-BR", label: "Portuguese"
85
+ end.to_s
86
+
87
+ expect(actual).to eq(%(<audio src="/assets/song.ogg"><track kind="captions" src="/assets/song.pt-BR.vtt" srclang="pt-BR" label="Portuguese"></audio>))
88
+ end
89
+
90
+ xit "renders with sources" do
91
+ actual = audio do
92
+ tag.text "Your browser does not support the audio tag"
93
+ tag.source src: subject.asset_url("song.ogg"), type: "audio/ogg"
94
+ tag.source src: subject.asset_url("song.wav"), type: "audio/wav"
95
+ end.to_s
96
+
97
+ expect(actual).to eq(%(<audio>Your browser does not support the audio tag<source src="/assets/song.ogg" type="audio/ogg"><source src="/assets/song.wav" type="audio/wav"></audio>))
98
+ end
99
+
100
+ it "raises an exception when no arguments" do
101
+ expect do
102
+ audio
103
+ end.to raise_error(
104
+ ArgumentError,
105
+ "You should provide a source via `src` option or with a `source` HTML tag"
106
+ )
107
+ end
108
+
109
+ it "raises an exception when no src and no block" do
110
+ expect do
111
+ audio(controls: true)
112
+ end.to raise_error(
113
+ ArgumentError,
114
+ "You should provide a source via `src` option or with a `source` HTML tag"
115
+ )
116
+ end
117
+
118
+ describe "cdn mode" do
119
+ let(:base_url) { "https://hanami.test" }
120
+
121
+ def before_prepare
122
+ Hanami.app.config.assets.base_url = base_url
123
+ end
124
+
125
+ it "returns absolute url for src attribute" do
126
+ actual = audio("song.ogg").to_s
127
+ expect(actual).to eq(%(<audio src="#{base_url}/assets/song.ogg"></audio>))
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def tag(...)
134
+ subject.__send__(:tag, ...)
135
+ end
136
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Hanami::Helpers::AssetsHelper, "#favicon", :app_integration do
4
+ subject(:obj) {
5
+ helpers = described_class
6
+ Class.new {
7
+ include helpers
8
+
9
+ attr_reader :_context
10
+
11
+ def initialize(context)
12
+ @_context = context
13
+ end
14
+ }.new(context)
15
+ }
16
+
17
+ def favicon(...)
18
+ obj.instance_eval { favicon(...) }
19
+ end
20
+
21
+ let(:root) { make_tmp_directory }
22
+ let(:context) { TestApp::Views::Context.new }
23
+
24
+ before do
25
+ with_directory(root) do
26
+ write "config/app.rb", <<~RUBY
27
+ module TestApp
28
+ class App < Hanami::App
29
+ config.logger.stream = StringIO.new
30
+ end
31
+ end
32
+ RUBY
33
+
34
+ write "app/views/context.rb", <<~RUBY
35
+ # auto_register: false
36
+
37
+ require "hanami/view/context"
38
+
39
+ module TestApp
40
+ module Views
41
+ class Context < Hanami::View::Context
42
+ end
43
+ end
44
+ end
45
+ RUBY
46
+
47
+ stub_assets("favicon.ico", "favicon.png")
48
+
49
+ require "hanami/setup"
50
+ before_prepare if respond_to?(:before_prepare)
51
+ require "hanami/prepare"
52
+ end
53
+ end
54
+
55
+ it "returns an instance of SafeString" do
56
+ actual = favicon
57
+ expect(actual).to be_instance_of(::Hanami::View::HTML::SafeString)
58
+ end
59
+
60
+ it "is aliased as #favicon_link_tag" do
61
+ expect(subject.favicon_link_tag).to eq(favicon)
62
+ end
63
+
64
+ it "renders <link> tag" do
65
+ actual = favicon.to_s
66
+ expect(actual).to eq(%(<link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">))
67
+ end
68
+
69
+ it "renders with HTML attributes" do
70
+ actual = favicon("favicon.png", rel: "icon", type: "image/png").to_s
71
+ expect(actual).to eq(%(<link href="/assets/favicon.png" rel="icon" type="image/png">))
72
+ end
73
+
74
+ it "ignores href passed as an option" do
75
+ actual = favicon("favicon.png", href: "wrong").to_s
76
+ expect(actual).to eq(%(<link href="/assets/favicon.png" rel="shortcut icon" type="image/x-icon">))
77
+ end
78
+
79
+ describe "cdn mode" do
80
+ let(:base_url) { "https://hanami.test" }
81
+
82
+ def before_prepare
83
+ Hanami.app.config.assets.base_url = "https://hanami.test"
84
+ end
85
+
86
+ it "returns absolute url for href attribute" do
87
+ actual = favicon.to_s
88
+ expect(actual).to eq(%(<link href="#{base_url}/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">))
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Hanami::Helpers::AssetsHelper, "#image", :app_integration do
4
+ subject(:obj) {
5
+ helpers = described_class
6
+ Class.new {
7
+ include helpers
8
+
9
+ attr_reader :_context
10
+
11
+ def initialize(context)
12
+ @_context = context
13
+ end
14
+ }.new(context)
15
+ }
16
+
17
+ def image(...)
18
+ subject.image(...)
19
+ end
20
+
21
+ let(:root) { make_tmp_directory }
22
+ let(:context) { TestApp::Views::Context.new }
23
+
24
+ before do
25
+ with_directory(root) do
26
+ write "config/app.rb", <<~RUBY
27
+ module TestApp
28
+ class App < Hanami::App
29
+ config.logger.stream = StringIO.new
30
+ end
31
+ end
32
+ RUBY
33
+
34
+ write "app/views/context.rb", <<~RUBY
35
+ # auto_register: false
36
+
37
+ require "hanami/view/context"
38
+
39
+ module TestApp
40
+ module Views
41
+ class Context < Hanami::View::Context
42
+ end
43
+ end
44
+ end
45
+ RUBY
46
+
47
+ stub_assets("application.jpg")
48
+
49
+ require "hanami/setup"
50
+ before_prepare if respond_to?(:before_prepare)
51
+ require "hanami/prepare"
52
+ end
53
+ end
54
+
55
+ it "returns an instance of HtmlBuilder" do
56
+ actual = image("application.jpg")
57
+ expect(actual).to be_instance_of(::Hanami::View::HTML::SafeString)
58
+ end
59
+
60
+ it "renders an <img> tag" do
61
+ actual = image("application.jpg").to_s
62
+ expect(actual).to eq(%(<img src="/assets/application.jpg" alt="Application">))
63
+ end
64
+
65
+ it "is aliased as #image_tag" do
66
+ expect(subject.image_tag("application.jpg")).to eq image("application.jpg")
67
+ end
68
+
69
+ it "custom alt" do
70
+ actual = image("application.jpg", alt: "My Alt").to_s
71
+ expect(actual).to eq(%(<img src="/assets/application.jpg" alt="My Alt">))
72
+ end
73
+
74
+ it "custom data attribute" do
75
+ actual = image("application.jpg", "data-user-id" => 5).to_s
76
+ expect(actual).to eq(%(<img src="/assets/application.jpg" alt="Application" data-user-id="5">))
77
+ end
78
+
79
+ it "ignores src passed as an option" do
80
+ actual = image("application.jpg", src: "wrong").to_s
81
+ expect(actual).to eq(%(<img src="/assets/application.jpg" alt="Application">))
82
+ end
83
+
84
+ describe "cdn mode" do
85
+ let(:base_url) { "https://hanami.test" }
86
+
87
+ def before_prepare
88
+ Hanami.app.config.assets.base_url = "https://hanami.test"
89
+ end
90
+
91
+ it "returns absolute url for src attribute" do
92
+ actual = image("application.jpg").to_s
93
+ expect(actual).to eq(%(<img src="#{base_url}/assets/application.jpg" alt="Application">))
94
+ end
95
+ end
96
+ end