hanami 2.1.0.beta1 → 2.1.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -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 +752 -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_tag_spec.rb +132 -0
  38. data/spec/unit/hanami/helpers/assets_helper/favicon_link_tag_spec.rb +91 -0
  39. data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +92 -0
  40. data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +143 -0
  41. data/spec/unit/hanami/helpers/assets_helper/stylesheet_link_tag_spec.rb +126 -0
  42. data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +132 -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,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Hanami::Helpers::AssetsHelper, "#audio_tag", :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_tag(...)
18
+ subject.audio_tag(...)
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_tag("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_tag("song.ogg").to_s
62
+ expect(actual).to eq(%(<audio src="/assets/song.ogg"></audio>))
63
+ end
64
+
65
+ it "renders with html attributes" do
66
+ actual = audio_tag("song.ogg", autoplay: true, controls: true).to_s
67
+ expect(actual).to eq(%(<audio autoplay="autoplay" controls="controls" src="/assets/song.ogg"></audio>))
68
+ end
69
+
70
+ it "renders with fallback content" do
71
+ actual = audio_tag("song.ogg") do
72
+ "Your browser does not support the audio tag"
73
+ end.to_s
74
+
75
+ expect(actual).to eq(%(<audio src="/assets/song.ogg">Your browser does not support the audio tag</audio>))
76
+ end
77
+
78
+ it "renders with tracks" do
79
+ actual = audio_tag("song.ogg") do
80
+ tag.track kind: "captions", src: subject.asset_url("song.pt-BR.vtt"), srclang: "pt-BR", label: "Portuguese"
81
+ end.to_s
82
+
83
+ expect(actual).to eq(%(<audio src="/assets/song.ogg"><track kind="captions" src="/assets/song.pt-BR.vtt" srclang="pt-BR" label="Portuguese"></audio>))
84
+ end
85
+
86
+ xit "renders with sources" do
87
+ actual = audio_tag do
88
+ tag.text "Your browser does not support the audio tag"
89
+ tag.source src: subject.asset_url("song.ogg"), type: "audio/ogg"
90
+ tag.source src: subject.asset_url("song.wav"), type: "audio/wav"
91
+ end.to_s
92
+
93
+ 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>))
94
+ end
95
+
96
+ it "raises an exception when no arguments" do
97
+ expect do
98
+ audio_tag
99
+ end.to raise_error(
100
+ ArgumentError,
101
+ "You should provide a source via `src` option or with a `source` HTML tag"
102
+ )
103
+ end
104
+
105
+ it "raises an exception when no src and no block" do
106
+ expect do
107
+ audio_tag(controls: true)
108
+ end.to raise_error(
109
+ ArgumentError,
110
+ "You should provide a source via `src` option or with a `source` HTML tag"
111
+ )
112
+ end
113
+
114
+ describe "cdn mode" do
115
+ let(:base_url) { "https://hanami.test" }
116
+
117
+ def before_prepare
118
+ Hanami.app.config.assets.base_url = base_url
119
+ end
120
+
121
+ it "returns absolute url for src attribute" do
122
+ actual = audio_tag("song.ogg").to_s
123
+ expect(actual).to eq(%(<audio src="#{base_url}/assets/song.ogg"></audio>))
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def tag(...)
130
+ subject.__send__(:tag, ...)
131
+ end
132
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Hanami::Helpers::AssetsHelper, "#favicon_link_tag", :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_link_tag(...)
18
+ obj.instance_eval { favicon_link_tag(...) }
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_link_tag
57
+ expect(actual).to be_instance_of(::Hanami::View::HTML::SafeString)
58
+ end
59
+
60
+ it "is aliased as `favicon`" do
61
+ expect(subject.favicon).to eq favicon_link_tag
62
+ end
63
+
64
+ it "renders <link> tag" do
65
+ actual = favicon_link_tag.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_link_tag("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_link_tag("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_link_tag.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,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Hanami::Helpers::AssetsHelper, "#image_tag", :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_tag(...)
18
+ subject.image_tag(...)
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_tag("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_tag("application.jpg").to_s
62
+ expect(actual).to eq(%(<img src="/assets/application.jpg" alt="Application">))
63
+ end
64
+
65
+ it "custom alt" do
66
+ actual = image_tag("application.jpg", alt: "My Alt").to_s
67
+ expect(actual).to eq(%(<img src="/assets/application.jpg" alt="My Alt">))
68
+ end
69
+
70
+ it "custom data attribute" do
71
+ actual = image_tag("application.jpg", "data-user-id" => 5).to_s
72
+ expect(actual).to eq(%(<img src="/assets/application.jpg" alt="Application" data-user-id="5">))
73
+ end
74
+
75
+ it "ignores src passed as an option" do
76
+ actual = image_tag("application.jpg", src: "wrong").to_s
77
+ expect(actual).to eq(%(<img src="/assets/application.jpg" alt="Application">))
78
+ end
79
+
80
+ describe "cdn mode" do
81
+ let(:base_url) { "https://hanami.test" }
82
+
83
+ def before_prepare
84
+ Hanami.app.config.assets.base_url = "https://hanami.test"
85
+ end
86
+
87
+ it "returns absolute url for src attribute" do
88
+ actual = image_tag("application.jpg").to_s
89
+ expect(actual).to eq(%(<img src="#{base_url}/assets/application.jpg" alt="Application">))
90
+ end
91
+ end
92
+ end