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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/LICENSE.md +1 -1
- data/README.md +25 -9
- data/hanami.gemspec +2 -2
- data/lib/hanami/config/actions.rb +0 -4
- data/lib/hanami/config/views.rb +0 -4
- data/lib/hanami/config.rb +54 -0
- data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
- data/lib/hanami/extensions/action.rb +4 -4
- data/lib/hanami/extensions/router/errors.rb +58 -0
- data/lib/hanami/extensions/view/context.rb +129 -60
- data/lib/hanami/extensions/view/part.rb +26 -0
- data/lib/hanami/extensions/view/scope.rb +26 -0
- data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
- data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
- data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
- data/lib/hanami/extensions/view/standard_helpers.rb +14 -0
- data/lib/hanami/extensions.rb +10 -3
- data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
- data/lib/hanami/helpers/form_helper/values.rb +75 -0
- data/lib/hanami/helpers/form_helper.rb +213 -0
- data/lib/hanami/middleware/public_errors_app.rb +75 -0
- data/lib/hanami/middleware/render_errors.rb +93 -0
- data/lib/hanami/slice.rb +27 -2
- data/lib/hanami/slice_configurable.rb +3 -2
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +1 -1
- data/lib/hanami.rb +1 -1
- data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
- data/spec/integration/action/view_rendering_spec.rb +0 -18
- data/spec/integration/rack_app/middleware_spec.rb +23 -23
- data/spec/integration/rack_app/rack_app_spec.rb +5 -1
- data/spec/integration/view/config/default_context_spec.rb +149 -0
- data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
- data/spec/integration/view/config/part_class_spec.rb +147 -0
- data/spec/integration/view/config/part_namespace_spec.rb +103 -0
- data/spec/integration/view/config/paths_spec.rb +119 -0
- data/spec/integration/view/config/scope_class_spec.rb +147 -0
- data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
- data/spec/integration/view/config/template_spec.rb +38 -0
- data/spec/integration/view/context/request_spec.rb +3 -7
- data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
- data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
- data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
- data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
- data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
- data/spec/integration/view/slice_configuration_spec.rb +9 -9
- data/spec/integration/web/render_detailed_errors_spec.rb +90 -0
- data/spec/integration/web/render_errors_spec.rb +240 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/matchers.rb +32 -0
- data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -4
- data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
- data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
- data/spec/unit/hanami/config/views_spec.rb +0 -18
- data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
- data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
- data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
- data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
- data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
- data/spec/unit/hanami/version_spec.rb +1 -1
- data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
- metadata +65 -33
- data/spec/integration/action/view_integration_spec.rb +0 -165
- data/spec/integration/view/part_namespace_spec.rb +0 -96
- data/spec/integration/view/path_spec.rb +0 -56
- data/spec/integration/view/template_spec.rb +0 -68
- data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
- data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
- data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
- data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
- data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
- data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
- 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.
|
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
|