hanami 2.0.3 → 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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