hanami 2.0.3 → 2.1.0.beta2

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -2
  3. data/LICENSE.md +1 -1
  4. data/README.md +26 -10
  5. data/hanami.gemspec +2 -2
  6. data/lib/hanami/app.rb +5 -0
  7. data/lib/hanami/config/actions.rb +4 -11
  8. data/lib/hanami/config/assets.rb +84 -0
  9. data/lib/hanami/config/null_config.rb +3 -0
  10. data/lib/hanami/config/views.rb +0 -4
  11. data/lib/hanami/config.rb +71 -5
  12. data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
  13. data/lib/hanami/extensions/action.rb +8 -6
  14. data/lib/hanami/extensions/router/errors.rb +58 -0
  15. data/lib/hanami/extensions/view/context.rb +129 -60
  16. data/lib/hanami/extensions/view/part.rb +26 -0
  17. data/lib/hanami/extensions/view/scope.rb +26 -0
  18. data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
  19. data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
  20. data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
  21. data/lib/hanami/extensions/view/standard_helpers.rb +18 -0
  22. data/lib/hanami/extensions.rb +10 -3
  23. data/lib/hanami/helpers/assets_helper.rb +752 -0
  24. data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
  25. data/lib/hanami/helpers/form_helper/values.rb +75 -0
  26. data/lib/hanami/helpers/form_helper.rb +213 -0
  27. data/lib/hanami/middleware/assets.rb +21 -0
  28. data/lib/hanami/middleware/public_errors_app.rb +75 -0
  29. data/lib/hanami/middleware/render_errors.rb +90 -0
  30. data/lib/hanami/providers/assets.rb +44 -0
  31. data/lib/hanami/rake_tasks.rb +19 -18
  32. data/lib/hanami/settings.rb +1 -1
  33. data/lib/hanami/slice.rb +48 -2
  34. data/lib/hanami/slice_configurable.rb +3 -2
  35. data/lib/hanami/version.rb +1 -1
  36. data/lib/hanami/web/rack_logger.rb +1 -1
  37. data/lib/hanami.rb +3 -3
  38. data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
  39. data/spec/integration/action/view_rendering_spec.rb +0 -18
  40. data/spec/integration/assets/assets_spec.rb +101 -0
  41. data/spec/integration/assets/serve_static_assets_spec.rb +152 -0
  42. data/spec/integration/logging/exception_logging_spec.rb +115 -0
  43. data/spec/integration/logging/notifications_spec.rb +68 -0
  44. data/spec/integration/logging/request_logging_spec.rb +128 -0
  45. data/spec/integration/rack_app/middleware_spec.rb +22 -22
  46. data/spec/integration/rack_app/rack_app_spec.rb +3 -220
  47. data/spec/integration/rake_tasks_spec.rb +107 -0
  48. data/spec/integration/view/config/default_context_spec.rb +149 -0
  49. data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
  50. data/spec/integration/view/config/part_class_spec.rb +147 -0
  51. data/spec/integration/view/config/part_namespace_spec.rb +103 -0
  52. data/spec/integration/view/config/paths_spec.rb +119 -0
  53. data/spec/integration/view/config/scope_class_spec.rb +147 -0
  54. data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
  55. data/spec/integration/view/config/template_spec.rb +38 -0
  56. data/spec/integration/view/context/assets_spec.rb +3 -9
  57. data/spec/integration/view/context/request_spec.rb +3 -7
  58. data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
  59. data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
  60. data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
  61. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
  62. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
  63. data/spec/integration/view/slice_configuration_spec.rb +9 -9
  64. data/spec/integration/web/render_detailed_errors_spec.rb +107 -0
  65. data/spec/integration/web/render_errors_spec.rb +242 -0
  66. data/spec/spec_helper.rb +1 -1
  67. data/spec/support/app_integration.rb +46 -2
  68. data/spec/support/matchers.rb +32 -0
  69. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +24 -36
  70. data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +4 -3
  71. data/spec/unit/hanami/config/actions/default_values_spec.rb +3 -6
  72. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
  73. data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
  74. data/spec/unit/hanami/config/views_spec.rb +0 -18
  75. data/spec/unit/hanami/env_spec.rb +11 -25
  76. data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
  77. data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +109 -0
  78. data/spec/unit/hanami/helpers/assets_helper/audio_tag_spec.rb +132 -0
  79. data/spec/unit/hanami/helpers/assets_helper/favicon_link_tag_spec.rb +91 -0
  80. data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +92 -0
  81. data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +143 -0
  82. data/spec/unit/hanami/helpers/assets_helper/stylesheet_link_tag_spec.rb +126 -0
  83. data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +132 -0
  84. data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
  85. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
  86. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
  87. data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
  88. data/spec/unit/hanami/version_spec.rb +1 -1
  89. data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
  90. metadata +95 -35
  91. data/lib/hanami/assets/app_config.rb +0 -61
  92. data/lib/hanami/assets/config.rb +0 -53
  93. data/spec/integration/action/view_integration_spec.rb +0 -165
  94. data/spec/integration/view/part_namespace_spec.rb +0 -96
  95. data/spec/integration/view/path_spec.rb +0 -56
  96. data/spec/integration/view/template_spec.rb +0 -68
  97. data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
  98. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
  99. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
  100. data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
  101. data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
  102. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
  103. data/spec/isolation/hanami/boot/success_spec.rb +0 -50
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami"
4
+
5
+ RSpec.describe "App view / Config / Scope namespace", :app_integration do
6
+ before do
7
+ with_directory(make_tmp_directory) do
8
+ write "config/app.rb", <<~RUBY
9
+ module TestApp
10
+ class App < Hanami::App
11
+ end
12
+ end
13
+ RUBY
14
+
15
+ write "app/view.rb", <<~RUBY
16
+ # auto_register: false
17
+
18
+ require "hanami/view"
19
+
20
+ module TestApp
21
+ class View < Hanami::View
22
+ end
23
+ end
24
+ RUBY
25
+
26
+ before_prepare if respond_to?(:before_prepare)
27
+ require "hanami/prepare"
28
+ end
29
+ end
30
+
31
+ subject(:scope_namespace) { view_class.config.scope_namespace }
32
+
33
+ describe "app view" do
34
+ let(:view_class) { TestApp::View }
35
+
36
+ describe "no scope namespace defined" do
37
+ it "is nil" do
38
+ expect(scope_namespace).to be nil
39
+ end
40
+ end
41
+
42
+ describe "scope namespace defined" do
43
+ def before_prepare
44
+ write "app/views/scopes/player.rb", <<~RUBY
45
+ module TestApp
46
+ module Views
47
+ module Scopes
48
+ class Player < Hanami::View::Scope
49
+ end
50
+ end
51
+ end
52
+ end
53
+ RUBY
54
+ end
55
+
56
+ it "is the Views::Scopes namespace within the app" do
57
+ expect(scope_namespace).to eq TestApp::Views::Scopes
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "slice view" do
63
+ def before_prepare
64
+ write "slices/main/view.rb", <<~RUBY
65
+ # auto_register: false
66
+
67
+ module Main
68
+ class View < TestApp::View
69
+ end
70
+ end
71
+ RUBY
72
+ end
73
+
74
+ let(:view_class) { Main::View }
75
+
76
+ describe "no scope namespace defined" do
77
+ it "is nil" do
78
+ expect(scope_namespace).to be nil
79
+ end
80
+ end
81
+
82
+ describe "scope namespace defined" do
83
+ def before_prepare
84
+ super
85
+
86
+ write "slices/main/views/scopes/player.rb", <<~RUBY
87
+ module Main
88
+ module Views
89
+ module Scopes
90
+ class Player < Hanami::View::Scope
91
+ end
92
+ end
93
+ end
94
+ end
95
+ RUBY
96
+ end
97
+
98
+ it "is the Views::Scopes namespace within the slice" do
99
+ expect(scope_namespace).to eq Main::Views::Scopes
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami"
4
+
5
+ RSpec.describe "App view / Config / Template", :app_integration do
6
+ before do
7
+ module TestApp
8
+ class App < Hanami::App
9
+ config.root = "/test_app"
10
+ end
11
+ end
12
+
13
+ Hanami.app.instance_eval(&app_hook) if respond_to?(:app_hook)
14
+ Hanami.app.register_slice :main
15
+ Hanami.app.prepare
16
+
17
+ module TestApp
18
+ class View < Hanami::View
19
+ end
20
+ end
21
+
22
+ module TestApp
23
+ module Views
24
+ module Article
25
+ class Index < TestApp::View
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ subject(:template) { view_class.config.template }
33
+ let(:view_class) { TestApp::Views::Article::Index }
34
+
35
+ it "configures the tempalte to match the class name" do
36
+ expect(template).to eq "article/index"
37
+ end
38
+ end
@@ -24,21 +24,15 @@ RSpec.describe "App view / Context / Assets", :app_integration do
24
24
 
25
25
  describe "#assets" do
26
26
  context "without assets provider" do
27
- it "raises error" do
27
+ xit "raises error" do
28
+ allow(Hanami).to receive(:bundled?).with("hanami-assets").and_return(false)
29
+
28
30
  expect { context.assets }
29
31
  .to raise_error(Hanami::ComponentLoadError, /hanami-assets/)
30
32
  end
31
33
  end
32
34
 
33
35
  context "with assets provider" do
34
- before do
35
- Hanami.app.register_provider(:assets) do
36
- start do
37
- register "assets", Object.new
38
- end
39
- end
40
- end
41
-
42
36
  it "is the app assets by default" do
43
37
  expect(context.assets).to be TestApp::App[:assets]
44
38
  end
@@ -20,14 +20,10 @@ RSpec.describe "App view / Context / Request", :app_integration do
20
20
  let(:context_class) { TestApp::Views::Context }
21
21
 
22
22
  subject(:context) {
23
- context_class.new(
24
- request: request,
25
- response: response,
26
- )
23
+ context_class.new(request: request)
27
24
  }
28
25
 
29
26
  let(:request) { double(:request) }
30
- let(:response) { double(:response) }
31
27
 
32
28
  describe "#request" do
33
29
  it "is the provided request" do
@@ -51,10 +47,10 @@ RSpec.describe "App view / Context / Request", :app_integration do
51
47
  let(:flash) { double(:flash) }
52
48
 
53
49
  before do
54
- allow(response).to receive(:flash) { flash }
50
+ allow(request).to receive(:flash) { flash }
55
51
  end
56
52
 
57
- it "is the response's flash" do
53
+ it "is the request's flash" do
58
54
  expect(context.flash).to be flash
59
55
  end
60
56
  end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/test"
4
+ require "stringio"
5
+
6
+ RSpec.describe "Helpers / FormHelper", :app_integration do
7
+ include Rack::Test::Methods
8
+ let(:app) { Hanami.app }
9
+
10
+ before do
11
+ with_directory(make_tmp_directory) do
12
+ write "config/app.rb", <<~RUBY
13
+ module TestApp
14
+ class App < Hanami::App
15
+ config.logger.stream = StringIO.new
16
+ end
17
+ end
18
+ RUBY
19
+
20
+ write "config/routes.rb", <<~RUBY
21
+ module TestApp
22
+ class Routes < Hanami::Routes
23
+ get "posts/:id/edit", to: "posts.edit"
24
+ put "posts/:id", to: "posts.update"
25
+ end
26
+ end
27
+ RUBY
28
+
29
+ write "app/action.rb", <<~RUBY
30
+ # auto_register: false
31
+
32
+ require "hanami/action"
33
+
34
+ module TestApp
35
+ class Action < Hanami::Action
36
+ end
37
+ end
38
+ RUBY
39
+
40
+ write "app/view.rb", <<~RUBY
41
+ # auto_register: false
42
+
43
+ require "hanami/view"
44
+
45
+ module TestApp
46
+ class View < Hanami::View
47
+ config.layout = nil
48
+ end
49
+ end
50
+ RUBY
51
+
52
+ write "app/actions/posts/edit.rb", <<~RUBY
53
+ module TestApp
54
+ module Actions
55
+ module Posts
56
+ class Edit < TestApp::Action
57
+ end
58
+ end
59
+ end
60
+ end
61
+ RUBY
62
+
63
+ write "app/actions/posts/update.rb", <<~RUBY
64
+ module TestApp
65
+ module Actions
66
+ module Posts
67
+ class Update < TestApp::Action
68
+ def handle(request, response)
69
+ if valid?(request.params[:post])
70
+ response.redirect_to "/posts/x/edit"
71
+ else
72
+ response.render view
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def valid?(post)
79
+ post.to_h[:title].to_s.length > 0
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ RUBY
86
+
87
+ write "app/views/posts/edit.rb", <<~RUBY
88
+ module TestApp
89
+ module Views
90
+ module Posts
91
+ class Edit < TestApp::View
92
+ expose :post do
93
+ Struct.new(:title, :body).new("Hello <world>", "This is the post.")
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ RUBY
100
+
101
+ write "app/templates/posts/edit.html.erb", <<~ERB
102
+ <h1>Edit post</h1>
103
+
104
+ <%= form_for("/posts") do |f| %>
105
+ <div>
106
+ Title:
107
+ <%= f.text_field "post.title" %>
108
+ </div>
109
+ <div>
110
+ Body:
111
+ <%= f.text_area "post.body" %>
112
+ </div>
113
+ <% end %>
114
+ ERB
115
+
116
+ before_prepare if respond_to?(:before_prepare)
117
+ require "hanami/prepare"
118
+ end
119
+ end
120
+
121
+ it "does not have a _csrf_token field when no sessions are configured" do
122
+ get "/posts/123/edit"
123
+
124
+ html = Capybara.string(last_response.body)
125
+
126
+ expect(html).to have_no_selector("input[type='hidden'][name='_csrf_token']")
127
+ end
128
+
129
+ it "uses the value from the view's locals" do
130
+ get "/posts/123/edit"
131
+
132
+ html = Capybara.string(last_response.body)
133
+
134
+ title_field = html.find("input[name='post[title]']")
135
+ expect(title_field.value).to eq "Hello <world>"
136
+
137
+ body_field = html.find("textarea[name='post[body]']")
138
+ expect(body_field.value).to eq "This is the post."
139
+ end
140
+
141
+ it "prefers the values from the request params" do
142
+ put "/posts/123", post: {title: "", body: "This is the UPDATED post."}
143
+
144
+ html = Capybara.string(last_response.body)
145
+
146
+ title_field = html.find("input[name='post[title]']")
147
+ expect(title_field.value).to eq ""
148
+
149
+ body_field = html.find("textarea[name='post[body]']")
150
+ expect(body_field.value).to eq "This is the UPDATED post."
151
+ end
152
+
153
+ context "sessions enabled" do
154
+ def before_prepare
155
+ write "config/app.rb", <<~RUBY
156
+ module TestApp
157
+ class App < Hanami::App
158
+ config.logger.stream = StringIO.new
159
+ config.actions.sessions = :cookie, {secret: "xyz"}
160
+ end
161
+ end
162
+ RUBY
163
+ end
164
+
165
+ it "inserts a CSRF token field" do
166
+ get "/posts/123/edit"
167
+
168
+ html = Capybara.string(last_response.body)
169
+
170
+ csrf_field = html.find("input[type='hidden'][name='_csrf_token']", visible: false)
171
+ expect(csrf_field.value).to match(/[a-z0-9]{10,}/i)
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+
5
+ # rubocop:disable Style/OpenStructUse
6
+
7
+ RSpec.describe "App view / Helpers / Part helpers", :app_integration do
8
+ before do
9
+ with_directory(make_tmp_directory) do
10
+ write "config/app.rb", <<~RUBY
11
+ module TestApp
12
+ class App < Hanami::App
13
+ end
14
+ end
15
+ RUBY
16
+
17
+ write "app/view.rb", <<~RUBY
18
+ # auto_register: false
19
+
20
+ require "hanami/view"
21
+
22
+ module TestApp
23
+ class View < Hanami::View
24
+ config.layout = nil
25
+ end
26
+ end
27
+ RUBY
28
+
29
+ before_prepare if respond_to?(:before_prepare)
30
+ require "hanami/prepare"
31
+ end
32
+ end
33
+
34
+ describe "app view and parts" do
35
+ def before_prepare
36
+ write "app/views/posts/show.rb", <<~RUBY
37
+ module TestApp
38
+ module Views
39
+ module Posts
40
+ class Show < TestApp::View
41
+ expose :post
42
+ end
43
+ end
44
+ end
45
+ end
46
+ RUBY
47
+
48
+ write "app/views/parts/post.rb", <<~RUBY
49
+ module TestApp
50
+ module Views
51
+ module Parts
52
+ class Post < TestApp::Views::Part
53
+ def number
54
+ format_number(value.number)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ RUBY
61
+
62
+ write "app/templates/posts/show.html.erb", <<~ERB
63
+ <h1><%= post.number %></h1>
64
+ ERB
65
+ end
66
+
67
+ it "makes default helpers available in parts" do
68
+ post = OpenStruct.new(number: 12_345)
69
+ output = TestApp::App["views.posts.show"].call(post: post).to_s.strip
70
+
71
+ expect(output).to eq "<h1>12,345</h1>"
72
+ end
73
+ end
74
+
75
+ describe "slice view and parts" do
76
+ def before_prepare
77
+ write "slices/main/view.rb", <<~RUBY
78
+ module Main
79
+ class View < TestApp::View
80
+ end
81
+ end
82
+ RUBY
83
+
84
+ write "slices/main/views/posts/show.rb", <<~RUBY
85
+ module Main
86
+ module Views
87
+ module Posts
88
+ class Show < Main::View
89
+ expose :post
90
+ end
91
+ end
92
+ end
93
+ end
94
+ RUBY
95
+
96
+ write "slices/main/views/parts/post.rb", <<~RUBY
97
+ module Main
98
+ module Views
99
+ module Parts
100
+ class Post < Main::Views::Part
101
+ def number
102
+ format_number(value.number)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ RUBY
109
+
110
+ write "slices/main/templates/posts/show.html.erb", <<~ERB
111
+ <h1><%= post.number %></h1>
112
+ ERB
113
+ end
114
+
115
+ it "makes default helpers available in parts" do
116
+ post = OpenStruct.new(number: 12_345)
117
+ output = Main::Slice["views.posts.show"].call(post: post).to_s.strip
118
+
119
+ expect(output).to eq "<h1>12,345</h1>"
120
+ end
121
+ end
122
+ end
123
+
124
+ # rubocop:enable Style/OpenStructUse
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App view / Helpers / Scope helpers", :app_integration do
4
+ before do
5
+ with_directory(make_tmp_directory) do
6
+ write "config/app.rb", <<~RUBY
7
+ module TestApp
8
+ class App < Hanami::App
9
+ end
10
+ end
11
+ RUBY
12
+
13
+ write "app/view.rb", <<~RUBY
14
+ # auto_register: false
15
+
16
+ require "hanami/view"
17
+
18
+ module TestApp
19
+ class View < Hanami::View
20
+ config.layout = nil
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ before_prepare if respond_to?(:before_prepare)
26
+ require "hanami/prepare"
27
+ end
28
+ end
29
+
30
+ describe "app view" do
31
+ def before_prepare
32
+ write "app/views/posts/show.rb", <<~RUBY
33
+ module TestApp
34
+ module Views
35
+ module Posts
36
+ class Show < TestApp::View
37
+ end
38
+ end
39
+ end
40
+ end
41
+ RUBY
42
+
43
+ write "app/templates/posts/show.html.erb", <<~ERB
44
+ <h1><%= format_number(12_345) %></h1>
45
+ ERB
46
+ end
47
+
48
+ it "makes default helpers available in templates" do
49
+ output = TestApp::App["views.posts.show"].call.to_s.strip
50
+ expect(output).to eq "<h1>12,345</h1>"
51
+ end
52
+ end
53
+
54
+ describe "slice view" do
55
+ def before_prepare
56
+ write "slices/main/view.rb", <<~RUBY
57
+ module Main
58
+ class View < TestApp::View
59
+ end
60
+ end
61
+ RUBY
62
+
63
+ write "slices/main/views/posts/show.rb", <<~RUBY
64
+ module Main
65
+ module Views
66
+ module Posts
67
+ class Show < Main::View
68
+ end
69
+ end
70
+ end
71
+ end
72
+ RUBY
73
+
74
+ write "slices/main/templates/posts/show.html.erb", <<~ERB
75
+ <h1><%= format_number(12_345) %></h1>
76
+ ERB
77
+ end
78
+
79
+ it "makes default helpers available in templates" do
80
+ output = Main::Slice["views.posts.show"].call.to_s.strip
81
+ expect(output).to eq "<h1>12,345</h1>"
82
+ end
83
+ end
84
+ end