hanami 2.0.3 → 2.1.0.beta1

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +25 -9
  5. data/hanami.gemspec +2 -2
  6. data/lib/hanami/config/actions.rb +0 -4
  7. data/lib/hanami/config/views.rb +0 -4
  8. data/lib/hanami/config.rb +54 -0
  9. data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
  10. data/lib/hanami/extensions/action.rb +4 -4
  11. data/lib/hanami/extensions/router/errors.rb +58 -0
  12. data/lib/hanami/extensions/view/context.rb +129 -60
  13. data/lib/hanami/extensions/view/part.rb +26 -0
  14. data/lib/hanami/extensions/view/scope.rb +26 -0
  15. data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
  16. data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
  17. data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
  18. data/lib/hanami/extensions/view/standard_helpers.rb +14 -0
  19. data/lib/hanami/extensions.rb +10 -3
  20. data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
  21. data/lib/hanami/helpers/form_helper/values.rb +75 -0
  22. data/lib/hanami/helpers/form_helper.rb +213 -0
  23. data/lib/hanami/middleware/public_errors_app.rb +75 -0
  24. data/lib/hanami/middleware/render_errors.rb +93 -0
  25. data/lib/hanami/slice.rb +27 -2
  26. data/lib/hanami/slice_configurable.rb +3 -2
  27. data/lib/hanami/version.rb +1 -1
  28. data/lib/hanami/web/rack_logger.rb +1 -1
  29. data/lib/hanami.rb +1 -1
  30. data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
  31. data/spec/integration/action/view_rendering_spec.rb +0 -18
  32. data/spec/integration/rack_app/middleware_spec.rb +23 -23
  33. data/spec/integration/rack_app/rack_app_spec.rb +5 -1
  34. data/spec/integration/view/config/default_context_spec.rb +149 -0
  35. data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
  36. data/spec/integration/view/config/part_class_spec.rb +147 -0
  37. data/spec/integration/view/config/part_namespace_spec.rb +103 -0
  38. data/spec/integration/view/config/paths_spec.rb +119 -0
  39. data/spec/integration/view/config/scope_class_spec.rb +147 -0
  40. data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
  41. data/spec/integration/view/config/template_spec.rb +38 -0
  42. data/spec/integration/view/context/request_spec.rb +3 -7
  43. data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
  44. data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
  45. data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
  46. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
  47. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
  48. data/spec/integration/view/slice_configuration_spec.rb +9 -9
  49. data/spec/integration/web/render_detailed_errors_spec.rb +90 -0
  50. data/spec/integration/web/render_errors_spec.rb +240 -0
  51. data/spec/spec_helper.rb +1 -1
  52. data/spec/support/matchers.rb +32 -0
  53. data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -4
  54. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
  55. data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
  56. data/spec/unit/hanami/config/views_spec.rb +0 -18
  57. data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
  58. data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
  59. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
  60. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
  61. data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
  62. data/spec/unit/hanami/version_spec.rb +1 -1
  63. data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
  64. metadata +65 -33
  65. data/spec/integration/action/view_integration_spec.rb +0 -165
  66. data/spec/integration/view/part_namespace_spec.rb +0 -96
  67. data/spec/integration/view/path_spec.rb +0 -56
  68. data/spec/integration/view/template_spec.rb +0 -68
  69. data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
  70. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
  71. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
  72. data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
  73. data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
  74. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
  75. data/spec/isolation/hanami/boot/success_spec.rb +0 -50
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rack/test"
5
+
6
+ RSpec.describe "Web / Rendering errors", :app_integration do
7
+ include Rack::Test::Methods
8
+
9
+ let(:app) { Hanami.app }
10
+
11
+ before do
12
+ with_directory(@dir = make_tmp_directory) do
13
+ write "config/app.rb", <<~RUBY
14
+ require "hanami"
15
+
16
+ module TestApp
17
+ class App < Hanami::App
18
+ config.logger.stream = File.new("/dev/null", "w")
19
+ config.render_errors = true
20
+ config.render_detailed_errors = false
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ write "config/routes.rb", <<~RUBY
26
+ module TestApp
27
+ class Routes < Hanami::Routes
28
+ get "index", to: "index"
29
+ get "error", to: "error"
30
+ end
31
+ end
32
+ RUBY
33
+
34
+ write "app/actions/index.rb", <<~RUBY
35
+ module TestApp
36
+ module Actions
37
+ class Index < Hanami::Action
38
+ def handle(*, response)
39
+ response.body = "Hello"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ RUBY
45
+
46
+ write "app/actions/error.rb", <<~RUBY
47
+ module TestApp
48
+ module Actions
49
+ class Error < Hanami::Action
50
+ def handle(*)
51
+ raise "oops"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ RUBY
57
+
58
+ before_prepare if respond_to?(:before_prepare)
59
+ require "hanami/prepare"
60
+ end
61
+ end
62
+
63
+ describe "HTML request" do
64
+ context "error pages present" do
65
+ def before_prepare
66
+ write "public/404.html", <<~HTML
67
+ <h1>Not found</h1>
68
+ HTML
69
+
70
+ write "public/500.html", <<~HTML
71
+ <h1>Error</h1>
72
+ HTML
73
+ end
74
+
75
+ it "responds with the HTML for a 404 from a not found error" do
76
+ get "/__not_found__"
77
+
78
+ expect(last_response.status).to eq 404
79
+ expect(last_response.body.strip).to eq "<h1>Not found</h1>"
80
+ expect(last_response.get_header("Content-Type")).to eq "text/html; charset=utf-8"
81
+ expect(last_response.get_header("Content-Length")).to eq "19"
82
+ end
83
+
84
+ it "responds with the HTML for a 404 from a method not allowed error" do
85
+ post "/index"
86
+
87
+ expect(last_response.status).to eq 404
88
+ expect(last_response.body.strip).to eq "<h1>Not found</h1>"
89
+ expect(last_response.get_header("Content-Type")).to eq "text/html; charset=utf-8"
90
+ expect(last_response.get_header("Content-Length")).to eq "19"
91
+ end
92
+
93
+ it "responds with the HTML for a 500" do
94
+ get "/error"
95
+
96
+ expect(last_response.status).to eq 500
97
+ expect(last_response.body.strip).to eq "<h1>Error</h1>"
98
+ expect(last_response.get_header("Content-Type")).to eq "text/html; charset=utf-8"
99
+ expect(last_response.get_header("Content-Length")).to eq "15"
100
+ end
101
+ end
102
+
103
+ context "error pages missing" do
104
+ it "responds with default text for a 404 from a not found error" do
105
+ get "/__not_found__"
106
+
107
+ expect(last_response.status).to eq 404
108
+ expect(last_response.body.strip).to eq "Not Found"
109
+ expect(last_response.get_header("Content-Type")).to eq "text/html; charset=utf-8"
110
+ expect(last_response.get_header("Content-Length")).to eq "9"
111
+ end
112
+
113
+ it "responds with default text for a 404 from a metohd not allowed error" do
114
+ post "/index"
115
+
116
+ expect(last_response.status).to eq 404
117
+ expect(last_response.body.strip).to eq "Not Found"
118
+ expect(last_response.get_header("Content-Type")).to eq "text/html; charset=utf-8"
119
+ expect(last_response.get_header("Content-Length")).to eq "9"
120
+ end
121
+
122
+ it "responds with default text for a 500" do
123
+ get "/error"
124
+
125
+ expect(last_response.status).to eq 500
126
+ expect(last_response.body.strip).to eq "Internal Server Error"
127
+ expect(last_response.get_header("Content-Type")).to eq "text/html; charset=utf-8"
128
+ expect(last_response.get_header("Content-Length")).to eq "21"
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "JSON request" do
134
+ it "renders a JSON response for a 404 from a not found error" do
135
+ get "/__not_found__", {}, "HTTP_ACCEPT" => "application/json"
136
+
137
+ expect(last_response.status).to eq 404
138
+ expect(last_response.body.strip).to eq %({"status":404,"error":"Not Found"})
139
+ expect(last_response.get_header("Content-Type")).to eq "application/json; charset=utf-8"
140
+ expect(last_response.get_header("Content-Length")).to eq "34"
141
+ end
142
+
143
+ it "renders a JSON response for a 404 from a metnod not allowed error" do
144
+ post "/index", {}, "HTTP_ACCEPT" => "application/json"
145
+
146
+ expect(last_response.status).to eq 404
147
+ expect(last_response.body.strip).to eq %({"status":404,"error":"Not Found"})
148
+ expect(last_response.get_header("Content-Type")).to eq "application/json; charset=utf-8"
149
+ expect(last_response.get_header("Content-Length")).to eq "34"
150
+ end
151
+
152
+ it "renders a JSON response for a 500" do
153
+ get "/error", {}, "HTTP_ACCEPT" => "application/json"
154
+
155
+ expect(last_response.status).to eq 500
156
+ expect(last_response.body.strip).to eq %({"status":500,"error":"Internal Server Error"})
157
+ expect(last_response.get_header("Content-Type")).to eq "application/json; charset=utf-8"
158
+ expect(last_response.get_header("Content-Length")).to eq "46"
159
+ end
160
+ end
161
+
162
+ describe "configuring error responses" do
163
+ def before_prepare
164
+ write "config/app.rb", <<~RUBY
165
+ require "hanami"
166
+
167
+ module TestApp
168
+ CustomNotFoundError = Class.new(StandardError)
169
+
170
+ class App < Hanami::App
171
+ config.logger.stream = File.new("/dev/null", "w")
172
+ config.render_errors = true
173
+ config.render_error_responses["TestApp::CustomNotFoundError"] = :not_found
174
+ config.render_detailed_errors = false
175
+ end
176
+ end
177
+ RUBY
178
+
179
+ write "app/actions/error.rb", <<~RUBY
180
+ module TestApp
181
+ module Actions
182
+ class Error < Hanami::Action
183
+ def handle(*)
184
+ raise CustomNotFoundError
185
+ end
186
+ end
187
+ end
188
+ end
189
+ RUBY
190
+
191
+ write "public/404.html", <<~HTML
192
+ <h1>Not found</h1>
193
+ HTML
194
+ end
195
+
196
+ it "uses the configured errors to determine the response" do
197
+ get "/error"
198
+
199
+ expect(last_response.status).to eq 404
200
+ expect(last_response.body.strip).to eq "<h1>Not found</h1>"
201
+ end
202
+ end
203
+
204
+ describe "render_errors config disabled" do
205
+ def before_prepare
206
+ write "config/app.rb", <<~RUBY
207
+ require "hanami"
208
+
209
+ module TestApp
210
+ class App < Hanami::App
211
+ config.logger.stream = File.new("/dev/null", "w")
212
+ config.render_errors = false
213
+ config.render_detailed_errors = false
214
+ end
215
+ end
216
+ RUBY
217
+
218
+ # Include error pages here to prove they are _not_ used
219
+ write "public/404.html", <<~HTML
220
+ <h1>Not found</h1>
221
+ HTML
222
+
223
+ write "public/500.html", <<~HTML
224
+ <h1>Error</h1>
225
+ HTML
226
+ end
227
+
228
+ it "raises a Hanami::Router::NotFoundError for a 404" do
229
+ expect { get "/__not_found__" }.to raise_error(Hanami::Router::NotFoundError)
230
+ end
231
+
232
+ it "raises a Hanami::Router::NotAllowedError for a 405" do
233
+ expect { post "/index" }.to raise_error(Hanami::Router::NotAllowedError)
234
+ end
235
+
236
+ it "raises the original error for a 500" do
237
+ expect { get "/error" }.to raise_error(RuntimeError, "oops")
238
+ end
239
+ end
240
+ end
data/spec/spec_helper.rb CHANGED
@@ -23,6 +23,6 @@ RSpec.configure do |config|
23
23
  # TODO: Find out what causes logger to create this dir when running specs.
24
24
  # There's probably a test app class being created somewhere with root
25
25
  # not pointing to a tmp dir.
26
- FileUtils.rm_r(LOG_DIR) if LOG_DIR.exist?
26
+ FileUtils.rm_rf(LOG_DIR) if LOG_DIR.exist?
27
27
  end
28
28
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Support
5
+ module Matchers
6
+ module HTML
7
+ def squish_html(str)
8
+ str
9
+ .gsub(/^[[:space:]]+/, "")
10
+ .gsub(/>[[:space:]]+</m, "><")
11
+ .strip
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ RSpec::Matchers.define :eq_html do |expected_html|
19
+ include RSpec::Support::Matchers::HTML
20
+
21
+ match do |actual_html|
22
+ squish_html(actual_html) == squish_html(expected_html)
23
+ end
24
+ end
25
+
26
+ RSpec::Matchers.define :include_html do |expected_html|
27
+ include RSpec::Support::Matchers::HTML
28
+
29
+ match do |actual_html|
30
+ squish_html(actual_html).include?(squish_html(expected_html))
31
+ end
32
+ end
@@ -13,10 +13,6 @@ RSpec.describe Hanami::Config::Actions, "default values" do
13
13
  specify { expect(config.name_inference_base).to eq "actions" }
14
14
  end
15
15
 
16
- describe "view_context_identifier" do
17
- specify { expect(config.view_context_identifier).to eq "views.context" }
18
- end
19
-
20
16
  describe "view_name_inferrer" do
21
17
  specify { expect(config.view_name_inferrer).to eq Hanami::Slice::ViewNameInferrer }
22
18
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+
5
+ RSpec.describe Hanami::Config, "#render_detailed_errors" do
6
+ let(:config) { described_class.new(app_name: app_name, env: env) }
7
+ let(:app_name) { Hanami::SliceName.new(double(name: "MyApp::App"), inflector: Dry::Inflector.new) }
8
+
9
+ subject(:render_detailed_errors) { config.render_detailed_errors }
10
+
11
+ context "development mode" do
12
+ let(:env) { :development }
13
+ it { is_expected.to be true }
14
+ end
15
+
16
+ context "test mode" do
17
+ let(:env) { :test }
18
+ it { is_expected.to be true }
19
+ end
20
+
21
+ context "production mode" do
22
+ let(:env) { :production }
23
+ it { is_expected.to be false }
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+
5
+ RSpec.describe Hanami::Config, "#render_errors" do
6
+ let(:config) { described_class.new(app_name: app_name, env: env) }
7
+ let(:app_name) { Hanami::SliceName.new(double(name: "MyApp::App"), inflector: Dry::Inflector.new) }
8
+
9
+ subject(:render_errors) { config.render_errors }
10
+
11
+ context "development mode" do
12
+ let(:env) { :development }
13
+ it { is_expected.to be false }
14
+ end
15
+
16
+ context "test mode" do
17
+ let(:env) { :test }
18
+ it { is_expected.to be false }
19
+ end
20
+
21
+ context "production mode" do
22
+ let(:env) { :production }
23
+ it { is_expected.to be true }
24
+ end
25
+ end
@@ -39,20 +39,6 @@ RSpec.describe Hanami::Config, "#views" do
39
39
  end
40
40
 
41
41
  describe "specialised default values" do
42
- describe "paths" do
43
- it 'is ["templates"]' do
44
- expect(views.paths).to match [
45
- an_object_satisfying { |path| path.dir.to_s == "templates" }
46
- ]
47
- end
48
- end
49
-
50
- describe "template_inference_base" do
51
- it 'is "views"' do
52
- expect(views.template_inference_base).to eq "views"
53
- end
54
- end
55
-
56
42
  describe "layout" do
57
43
  it 'is "app"' do
58
44
  expect(views.layout).to eq "app"
@@ -69,10 +55,6 @@ RSpec.describe Hanami::Config, "#views" do
69
55
  expect(views).to be_frozen
70
56
  end
71
57
 
72
- it "does not allow changes to locally defined settings" do
73
- expect { views.parts_path = "parts" }.to raise_error(Dry::Configurable::FrozenConfigError)
74
- end
75
-
76
58
  it "does not allow changes to base view settings" do
77
59
  expect { views.paths = [] }.to raise_error(Dry::Configurable::FrozenConfigError)
78
60
  end
@@ -0,0 +1,59 @@
1
+ require "hanami/view"
2
+ require "hanami/view/context"
3
+ require "hanami/extensions/view/context"
4
+
5
+ RSpec.describe(Hanami::View::Context) do
6
+ subject(:context) { described_class.new(**args) }
7
+ let(:args) { {} }
8
+
9
+ describe "#assets" do
10
+ context "assets given" do
11
+ let(:args) { {assets: assets} }
12
+ let(:assets) { double(:assets) }
13
+
14
+ it "returns the assets" do
15
+ expect(context.assets).to be assets
16
+ end
17
+ end
18
+
19
+ context "no assets given" do
20
+ it "raises a Hanami::ComponentLoadError" do
21
+ expect { context.assets }.to raise_error Hanami::ComponentLoadError
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "#request" do
27
+ context "request given" do
28
+ let(:args) { {request: request} }
29
+ let(:request) { double(:request) }
30
+
31
+ it "returns the request" do
32
+ expect(context.request).to be request
33
+ end
34
+ end
35
+
36
+ context "no request given" do
37
+ it "raises a Hanami::ComponentLoadError" do
38
+ expect { context.request }.to raise_error Hanami::ComponentLoadError
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "#routes" do
44
+ context "routes given" do
45
+ let(:args) { {routes: routes} }
46
+ let(:routes) { double(:routes) }
47
+
48
+ it "returns the routes" do
49
+ expect(context.routes).to be routes
50
+ end
51
+ end
52
+
53
+ context "no routes given" do
54
+ it "raises a Hanami::ComponentLoadError" do
55
+ expect { context.routes }.to raise_error Hanami::ComponentLoadError
56
+ end
57
+ end
58
+ end
59
+ end