hanami 2.0.0.alpha8 → 2.0.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 (224) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +442 -241
  3. data/FEATURES.md +30 -9
  4. data/README.md +1 -3
  5. data/hanami.gemspec +21 -11
  6. data/lib/hanami/app.rb +141 -0
  7. data/lib/hanami/assets/application_configuration.rb +10 -4
  8. data/lib/hanami/configuration/actions/content_security_policy.rb +5 -5
  9. data/lib/hanami/configuration/actions/cookies.rb +2 -2
  10. data/lib/hanami/configuration/actions.rb +10 -4
  11. data/lib/hanami/configuration/logger.rb +4 -4
  12. data/lib/hanami/configuration/router.rb +2 -6
  13. data/lib/hanami/configuration/sessions.rb +1 -1
  14. data/lib/hanami/configuration/views.rb +9 -4
  15. data/lib/hanami/configuration.rb +118 -46
  16. data/lib/hanami/constants.rb +24 -2
  17. data/lib/hanami/errors.rb +1 -1
  18. data/lib/hanami/{application → extensions}/action/slice_configured_action.rb +9 -9
  19. data/lib/hanami/extensions/action.rb +79 -0
  20. data/lib/hanami/extensions/view/context.rb +106 -0
  21. data/lib/hanami/{application → extensions}/view/slice_configured_context.rb +10 -10
  22. data/lib/hanami/{application → extensions}/view/slice_configured_view.rb +12 -6
  23. data/lib/hanami/extensions/view.rb +33 -0
  24. data/lib/hanami/extensions.rb +10 -0
  25. data/lib/hanami/providers/inflector.rb +13 -0
  26. data/lib/hanami/providers/logger.rb +13 -0
  27. data/lib/hanami/providers/rack.rb +27 -0
  28. data/lib/hanami/providers/routes.rb +33 -0
  29. data/lib/hanami/providers/settings.rb +23 -0
  30. data/lib/hanami/rake_tasks.rb +61 -0
  31. data/lib/hanami/routes.rb +51 -0
  32. data/lib/hanami/server.rb +1 -1
  33. data/lib/hanami/settings/dotenv_store.rb +58 -0
  34. data/lib/hanami/settings.rb +90 -0
  35. data/lib/hanami/setup.rb +4 -2
  36. data/lib/hanami/{application → slice}/router.rb +18 -13
  37. data/lib/hanami/slice/routes_helper.rb +37 -0
  38. data/lib/hanami/{application → slice}/routing/middleware/stack.rb +43 -5
  39. data/lib/hanami/slice/routing/resolver.rb +97 -0
  40. data/lib/hanami/{application → slice}/view_name_inferrer.rb +3 -3
  41. data/lib/hanami/slice.rb +246 -73
  42. data/lib/hanami/slice_configurable.rb +4 -17
  43. data/lib/hanami/slice_name.rb +6 -6
  44. data/lib/hanami/slice_registrar.rb +119 -0
  45. data/lib/hanami/version.rb +1 -1
  46. data/lib/hanami/web/rack_logger.rb +1 -1
  47. data/lib/hanami.rb +34 -26
  48. data/spec/integration/application_middleware_stack_spec.rb +84 -0
  49. data/spec/integration/assets/cdn_spec.rb +48 -0
  50. data/spec/integration/assets/fingerprint_spec.rb +42 -0
  51. data/spec/integration/assets/helpers_spec.rb +50 -0
  52. data/spec/integration/assets/serve_spec.rb +70 -0
  53. data/spec/integration/assets/subresource_integrity_spec.rb +54 -0
  54. data/spec/integration/body_parsers_spec.rb +50 -0
  55. data/spec/integration/cli/assets/precompile_spec.rb +147 -0
  56. data/spec/integration/cli/assets_spec.rb +14 -0
  57. data/spec/integration/cli/console_spec.rb +105 -0
  58. data/spec/integration/cli/db/apply_spec.rb +74 -0
  59. data/spec/integration/cli/db/console_spec.rb +40 -0
  60. data/spec/integration/cli/db/create_spec.rb +50 -0
  61. data/spec/integration/cli/db/drop_spec.rb +54 -0
  62. data/spec/integration/cli/db/migrate_spec.rb +108 -0
  63. data/spec/integration/cli/db/prepare_spec.rb +36 -0
  64. data/spec/integration/cli/db/rollback_spec.rb +96 -0
  65. data/spec/integration/cli/db/version_spec.rb +38 -0
  66. data/spec/integration/cli/db_spec.rb +21 -0
  67. data/spec/integration/cli/destroy/action_spec.rb +143 -0
  68. data/spec/integration/cli/destroy/app_spec.rb +118 -0
  69. data/spec/integration/cli/destroy/mailer_spec.rb +74 -0
  70. data/spec/integration/cli/destroy/migration_spec.rb +70 -0
  71. data/spec/integration/cli/destroy/model_spec.rb +113 -0
  72. data/spec/integration/cli/destroy_spec.rb +18 -0
  73. data/spec/integration/cli/generate/action_spec.rb +469 -0
  74. data/spec/integration/cli/generate/app_spec.rb +215 -0
  75. data/spec/integration/cli/generate/mailer_spec.rb +189 -0
  76. data/spec/integration/cli/generate/migration_spec.rb +72 -0
  77. data/spec/integration/cli/generate/model_spec.rb +290 -0
  78. data/spec/integration/cli/generate/secret_spec.rb +56 -0
  79. data/spec/integration/cli/generate_spec.rb +19 -0
  80. data/spec/integration/cli/new/database_spec.rb +235 -0
  81. data/spec/integration/cli/new/hanami_head_spec.rb +27 -0
  82. data/spec/integration/cli/new/template_spec.rb +118 -0
  83. data/spec/integration/cli/new/test_spec.rb +274 -0
  84. data/spec/integration/cli/new_spec.rb +970 -0
  85. data/spec/integration/cli/plugins_spec.rb +39 -0
  86. data/spec/integration/cli/routes_spec.rb +49 -0
  87. data/spec/integration/cli/server_spec.rb +626 -0
  88. data/spec/integration/cli/version_spec.rb +85 -0
  89. data/spec/integration/early_hints_spec.rb +35 -0
  90. data/spec/integration/handle_exceptions_spec.rb +244 -0
  91. data/spec/integration/head_spec.rb +89 -0
  92. data/spec/integration/http_headers_spec.rb +29 -0
  93. data/spec/integration/mailer_spec.rb +32 -0
  94. data/spec/integration/middleware_spec.rb +81 -0
  95. data/spec/integration/mount_applications_spec.rb +88 -0
  96. data/spec/integration/project_initializers_spec.rb +40 -0
  97. data/spec/integration/rackup_spec.rb +35 -0
  98. data/spec/integration/rake/with_minitest_spec.rb +67 -0
  99. data/spec/integration/rake/with_rspec_spec.rb +69 -0
  100. data/spec/integration/routing_helpers_spec.rb +61 -0
  101. data/spec/integration/security/content_security_policy_spec.rb +46 -0
  102. data/spec/integration/security/csrf_protection_spec.rb +42 -0
  103. data/spec/integration/security/force_ssl_spec.rb +29 -0
  104. data/spec/integration/security/x_content_type_options_spec.rb +46 -0
  105. data/spec/integration/security/x_frame_options_spec.rb +46 -0
  106. data/spec/integration/security/x_xss_protection_spec.rb +46 -0
  107. data/spec/integration/send_file_spec.rb +51 -0
  108. data/spec/integration/sessions_spec.rb +247 -0
  109. data/spec/integration/static_middleware_spec.rb +21 -0
  110. data/spec/integration/streaming_spec.rb +41 -0
  111. data/spec/integration/unsafe_send_file_spec.rb +52 -0
  112. data/spec/isolation/hanami/application/already_configured_spec.rb +19 -0
  113. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +10 -0
  114. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +14 -0
  115. data/spec/isolation/hanami/application/not_configured_spec.rb +9 -0
  116. data/spec/isolation/hanami/application/routes/configured_spec.rb +44 -0
  117. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +16 -0
  118. data/spec/isolation/hanami/boot/success_spec.rb +50 -0
  119. data/spec/new_integration/action/configuration_spec.rb +26 -0
  120. data/spec/new_integration/action/cookies_spec.rb +58 -0
  121. data/spec/new_integration/action/csrf_protection_spec.rb +54 -0
  122. data/spec/new_integration/action/routes_spec.rb +73 -0
  123. data/spec/new_integration/action/sessions_spec.rb +50 -0
  124. data/spec/new_integration/action/view_integration_spec.rb +165 -0
  125. data/spec/new_integration/action/view_rendering/automatic_rendering_spec.rb +247 -0
  126. data/spec/new_integration/action/view_rendering/paired_view_inference_spec.rb +115 -0
  127. data/spec/new_integration/action/view_rendering_spec.rb +107 -0
  128. data/spec/new_integration/code_loading/loading_from_app_spec.rb +152 -0
  129. data/spec/new_integration/code_loading/loading_from_slice_spec.rb +165 -0
  130. data/spec/new_integration/container/application_routes_helper_spec.rb +48 -0
  131. data/spec/new_integration/container/auto_injection_spec.rb +53 -0
  132. data/spec/new_integration/container/auto_registration_spec.rb +86 -0
  133. data/spec/new_integration/container/autoloader_spec.rb +80 -0
  134. data/spec/new_integration/container/imports_spec.rb +253 -0
  135. data/spec/new_integration/container/prepare_container_spec.rb +123 -0
  136. data/spec/new_integration/container/shutdown_spec.rb +91 -0
  137. data/spec/new_integration/container/standard_bootable_components_spec.rb +124 -0
  138. data/spec/new_integration/rack_app/middleware_spec.rb +215 -0
  139. data/spec/new_integration/rack_app/non_booted_rack_app_spec.rb +105 -0
  140. data/spec/new_integration/rack_app/rack_app_spec.rb +524 -0
  141. data/spec/new_integration/settings_spec.rb +115 -0
  142. data/spec/new_integration/slices/external_slice_spec.rb +92 -0
  143. data/spec/new_integration/slices/slice_configuration_spec.rb +40 -0
  144. data/spec/new_integration/slices/slice_routing_spec.rb +226 -0
  145. data/spec/new_integration/slices/slice_settings_spec.rb +141 -0
  146. data/spec/new_integration/slices_spec.rb +101 -0
  147. data/spec/new_integration/view/configuration_spec.rb +49 -0
  148. data/spec/new_integration/view/context/assets_spec.rb +67 -0
  149. data/spec/new_integration/view/context/inflector_spec.rb +48 -0
  150. data/spec/new_integration/view/context/request_spec.rb +61 -0
  151. data/spec/new_integration/view/context/routes_spec.rb +86 -0
  152. data/spec/new_integration/view/context/settings_spec.rb +50 -0
  153. data/spec/new_integration/view/inflector_spec.rb +57 -0
  154. data/spec/new_integration/view/part_namespace_spec.rb +96 -0
  155. data/spec/new_integration/view/path_spec.rb +56 -0
  156. data/spec/new_integration/view/template_spec.rb +68 -0
  157. data/spec/new_integration/view/views_spec.rb +103 -0
  158. data/spec/spec_helper.rb +16 -0
  159. data/spec/support/app_integration.rb +91 -0
  160. data/spec/support/coverage.rb +1 -0
  161. data/spec/support/fixtures/hanami-plugin/Gemfile +8 -0
  162. data/spec/support/fixtures/hanami-plugin/README.md +35 -0
  163. data/spec/support/fixtures/hanami-plugin/Rakefile +4 -0
  164. data/spec/support/fixtures/hanami-plugin/bin/console +15 -0
  165. data/spec/support/fixtures/hanami-plugin/bin/setup +8 -0
  166. data/spec/support/fixtures/hanami-plugin/hanami-plugin.gemspec +28 -0
  167. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/cli.rb +19 -0
  168. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/version.rb +7 -0
  169. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin.rb +8 -0
  170. data/spec/support/rspec.rb +27 -0
  171. data/spec/support/shared_examples/cli/generate/app.rb +494 -0
  172. data/spec/support/shared_examples/cli/generate/migration.rb +32 -0
  173. data/spec/support/shared_examples/cli/generate/model.rb +81 -0
  174. data/spec/support/shared_examples/cli/new.rb +97 -0
  175. data/spec/unit/hanami/configuration/actions/content_security_policy_spec.rb +102 -0
  176. data/spec/unit/hanami/configuration/actions/cookies_spec.rb +46 -0
  177. data/spec/unit/hanami/configuration/actions/csrf_protection_spec.rb +57 -0
  178. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +52 -0
  179. data/spec/unit/hanami/configuration/actions/sessions_spec.rb +50 -0
  180. data/spec/unit/hanami/configuration/actions_spec.rb +78 -0
  181. data/spec/unit/hanami/configuration/base_url_spec.rb +25 -0
  182. data/spec/unit/hanami/configuration/inflector_spec.rb +35 -0
  183. data/spec/unit/hanami/configuration/logger_spec.rb +203 -0
  184. data/spec/unit/hanami/configuration/views_spec.rb +120 -0
  185. data/spec/unit/hanami/configuration_spec.rb +43 -0
  186. data/spec/unit/hanami/env_spec.rb +54 -0
  187. data/spec/unit/hanami/routes_spec.rb +25 -0
  188. data/spec/unit/hanami/settings/dotenv_store_spec.rb +119 -0
  189. data/spec/unit/hanami/settings_spec.rb +56 -0
  190. data/spec/unit/hanami/slice_configurable_spec.rb +104 -0
  191. data/spec/unit/hanami/slice_name_spec.rb +47 -0
  192. data/spec/unit/hanami/slice_spec.rb +17 -0
  193. data/spec/unit/hanami/version_spec.rb +7 -0
  194. data/spec/unit/hanami/web/rack_logger_spec.rb +78 -0
  195. metadata +353 -57
  196. data/lib/hanami/application/action.rb +0 -72
  197. data/lib/hanami/application/container/providers/inflector.rb +0 -7
  198. data/lib/hanami/application/container/providers/logger.rb +0 -7
  199. data/lib/hanami/application/container/providers/rack_logger.rb +0 -15
  200. data/lib/hanami/application/container/providers/rack_monitor.rb +0 -12
  201. data/lib/hanami/application/container/providers/routes_helper.rb +0 -9
  202. data/lib/hanami/application/container/providers/settings.rb +0 -7
  203. data/lib/hanami/application/routes.rb +0 -55
  204. data/lib/hanami/application/routes_helper.rb +0 -34
  205. data/lib/hanami/application/routing/resolver/node.rb +0 -50
  206. data/lib/hanami/application/routing/resolver/trie.rb +0 -59
  207. data/lib/hanami/application/routing/resolver.rb +0 -87
  208. data/lib/hanami/application/routing/router.rb +0 -36
  209. data/lib/hanami/application/settings/dotenv_store.rb +0 -60
  210. data/lib/hanami/application/settings.rb +0 -93
  211. data/lib/hanami/application/slice_registrar.rb +0 -106
  212. data/lib/hanami/application/view/context.rb +0 -95
  213. data/lib/hanami/application/view.rb +0 -24
  214. data/lib/hanami/application.rb +0 -273
  215. data/lib/hanami/cli/application/cli.rb +0 -40
  216. data/lib/hanami/cli/application/command.rb +0 -47
  217. data/lib/hanami/cli/application/commands/console.rb +0 -81
  218. data/lib/hanami/cli/application/commands.rb +0 -16
  219. data/lib/hanami/cli/base_command.rb +0 -48
  220. data/lib/hanami/cli/commands/command.rb +0 -171
  221. data/lib/hanami/cli/commands/server.rb +0 -88
  222. data/lib/hanami/cli/commands.rb +0 -65
  223. data/lib/hanami/configuration/middleware.rb +0 -20
  224. data/lib/hanami/configuration/source_dirs.rb +0 -42
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App action / Routes", :app_integration do
4
+ specify "Access app routes from an action" do
5
+ with_tmp_directory(Dir.mktmpdir) do
6
+ write "config/app.rb", <<~RUBY
7
+ require "hanami"
8
+
9
+ module TestApp
10
+ class App < Hanami::App; end
11
+ end
12
+ RUBY
13
+
14
+ write "config/routes.rb", <<~RUBY
15
+ module TestApp
16
+ class Routes < Hanami::Routes
17
+ define do
18
+ root to: "home.index"
19
+
20
+ slice :admin, at: "/admin" do
21
+ root to: "dashboard.index"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ RUBY
27
+
28
+ write "app/action.rb", <<~RUBY
29
+ # auto_register: false
30
+
31
+ module TestApp
32
+ class Action < Hanami::Action; end
33
+ end
34
+ RUBY
35
+
36
+ write "app/actions/home/index.rb", <<~RUBY
37
+ module TestApp
38
+ module Actions
39
+ module Home
40
+ class Index < TestApp::Action
41
+ def handle(req, res)
42
+ res.body = routes.path(:root)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ RUBY
49
+
50
+ write "slices/admin/actions/dashboard/index.rb", <<~RUBY
51
+ module Admin
52
+ module Actions
53
+ module Dashboard
54
+ class Index < TestApp::Action
55
+ def handle(req, res)
56
+ res.body = routes.path(:admin_root)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ RUBY
63
+
64
+ require "hanami/prepare"
65
+
66
+ response = TestApp::App["actions.home.index"].call({})
67
+ expect(response.body).to eq ["/"]
68
+
69
+ response = Admin::Slice["actions.dashboard.index"].call({})
70
+ expect(response.body).to eq ["/admin"]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App action / Sessions", :app_integration do
4
+ before do
5
+ module TestApp
6
+ class App < Hanami::App
7
+ end
8
+ end
9
+
10
+ Hanami.app.instance_eval(&app_hook) if respond_to?(:app_hook)
11
+ Hanami.app.prepare
12
+
13
+ module TestApp
14
+ class Action < Hanami::Action
15
+ end
16
+ end
17
+ end
18
+
19
+ subject(:action_class) { TestApp::Action }
20
+
21
+ context "HTTP sessions enabled" do
22
+ let(:app_hook) {
23
+ proc do
24
+ config.actions.sessions = :cookie, {secret: "abc123"}
25
+ end
26
+ }
27
+
28
+ it "has HTTP sessions enabled" do
29
+ expect(action_class.ancestors).to include(Hanami::Action::Session)
30
+ end
31
+ end
32
+
33
+ context "HTTP sessions explicitly disabled" do
34
+ let(:app_hook) {
35
+ proc do
36
+ config.actions.sessions = nil
37
+ end
38
+ }
39
+
40
+ it "does not have HTTP sessions enabled" do
41
+ expect(action_class.ancestors.map(&:to_s)).not_to include("Hanami::Action::Session")
42
+ end
43
+ end
44
+
45
+ context "HTTP sessions not enabled" do
46
+ it "does not have HTTP session enabled" do
47
+ expect(action_class.ancestors.map(&:to_s)).not_to include("Hanami::Action::Session")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App action / View integration", :app_integration do
4
+ before do
5
+ module TestApp
6
+ class App < Hanami::App
7
+ end
8
+ end
9
+
10
+ Hanami.app.instance_eval(&app_hook) if respond_to?(:app_hook)
11
+ Hanami.app.prepare
12
+
13
+ module TestApp
14
+ class Action < Hanami::Action; end
15
+ end
16
+
17
+ module TestApp
18
+ module Actions
19
+ module Articles
20
+ class Index < TestApp::Action; end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ let(:action_class) { TestApp::Actions::Articles::Index }
27
+ subject(:action) { action_class.new(**action_args) }
28
+ let(:action_args) { {} }
29
+
30
+ describe "#view_context" do
31
+ subject(:view_context) { action.view_context }
32
+
33
+ context "Explicitly injected view context" do
34
+ let(:view_context) { double(:view_context) }
35
+ let(:action_args) { {view_context: view_context} }
36
+
37
+ it "returns the injected object" do
38
+ is_expected.to eql view_context
39
+ end
40
+ end
41
+
42
+ context "No view context registered" do
43
+ it "is nil" do
44
+ is_expected.to be_nil
45
+ end
46
+ end
47
+
48
+ context "Default view context identifier" do
49
+ context "View context registered in slice" do
50
+ before do
51
+ TestApp::App.register "views.context", slice_view_context
52
+ end
53
+
54
+ let(:slice_view_context) { double(:slice_view_context) }
55
+
56
+ it "is the slice's view context" do
57
+ is_expected.to eql slice_view_context
58
+ end
59
+ end
60
+
61
+ context "View context registered in app" do
62
+ before do
63
+ Hanami.app.register "views.context", app_view_context
64
+ end
65
+
66
+ let(:app_view_context) { double(:app_view_context) }
67
+
68
+ it "is the apps's view context" do
69
+ is_expected.to eql app_view_context
70
+ end
71
+ end
72
+ end
73
+
74
+ context "custom view context identifier" do
75
+ let(:app_hook) {
76
+ proc do |app|
77
+ app.config.actions.view_context_identifier = "view.custom_context"
78
+ end
79
+ }
80
+
81
+ before do
82
+ TestApp::App.register "view.custom_context", custom_view_context
83
+ end
84
+
85
+ let(:custom_view_context) { double(:custom_view_context) }
86
+
87
+ it "is the context registered with the custom identifier" do
88
+ is_expected.to eql custom_view_context
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "#view_options" do
94
+ subject(:view_options) { action.send(:view_options, req, res) }
95
+ let(:req) { double(:req) }
96
+ let(:res) { double(:res) }
97
+
98
+ context "without view context" do
99
+ it "is an empty hash" do
100
+ is_expected.to eq({})
101
+ end
102
+ end
103
+
104
+ context "with view context" do
105
+ let(:initial_view_context) { double(:initial_view_context) }
106
+ let(:action_args) { {view_context: initial_view_context} }
107
+
108
+ context "default #view_context_options" do
109
+ let(:request_view_context) { double(:request_view_context) }
110
+
111
+ before do
112
+ allow(initial_view_context).to receive(:with).with(
113
+ request: req,
114
+ response: res,
115
+ ) { request_view_context }
116
+ end
117
+
118
+ it "is the view context with the request and response provided" do
119
+ is_expected.to eq(context: request_view_context)
120
+ end
121
+ end
122
+
123
+ context "custom #view_context_options" do
124
+ let(:custom_view_context) { double(:custom_view_context)}
125
+
126
+ before do
127
+ action_class.class_eval do
128
+ def view_context_options(req, res)
129
+ {custom_option: "custom option"}
130
+ end
131
+ end
132
+
133
+ allow(initial_view_context).to receive(:with).with(
134
+ custom_option: "custom option"
135
+ ) { custom_view_context }
136
+ end
137
+
138
+ it "is the view context with the custom options provided" do
139
+ is_expected.to eq(context: custom_view_context)
140
+ end
141
+ end
142
+
143
+ context "specialized method (calling super) defined in action class" do
144
+ let(:request_view_context) { double(:request_view_context) }
145
+
146
+ before do
147
+ allow(initial_view_context).to receive(:with).with(
148
+ request: req,
149
+ response: res,
150
+ ) { request_view_context }
151
+
152
+ action_class.class_eval do
153
+ def view_options(req, res)
154
+ super.merge(extra_option: "extra option")
155
+ end
156
+ end
157
+ end
158
+
159
+ it "includes the options provided by the specialized method" do
160
+ is_expected.to eq(context: request_view_context, extra_option: "extra option")
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App action / View rendering / Automatic rendering", :app_integration do
4
+ it "Renders a view automatically, passing all params and exposures" do
5
+ within_app do
6
+ write "app/actions/profile/show.rb", <<~RUBY
7
+ module TestApp
8
+ module Actions
9
+ module Profile
10
+ class Show < TestApp::Action
11
+ def handle(req, res)
12
+ res[:favorite_number] = 123
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ RUBY
19
+
20
+ write "app/views/profile/show.rb", <<~RUBY
21
+ module TestApp
22
+ module Views
23
+ module Profile
24
+ class Show < TestApp::View
25
+ expose :name, :favorite_number
26
+ end
27
+ end
28
+ end
29
+ end
30
+ RUBY
31
+
32
+ write "app/templates/profile/show.html.slim", <<~'SLIM'
33
+ h1 Hello, #{name}. Your favorite number is #{favorite_number}, right?
34
+ SLIM
35
+
36
+ require "hanami/prepare"
37
+
38
+ action = TestApp::App["actions.profile.show"]
39
+ response = action.(name: "Jennifer")
40
+ rendered = response.body[0]
41
+
42
+ expect(rendered).to eq "<html><body><h1>Hello, Jennifer. Your favorite number is 123, right?</h1></body></html>"
43
+ expect(response.status).to eq 200
44
+ end
45
+ end
46
+
47
+ it "Does not render a view automatically when #render? returns false " do
48
+ within_app do
49
+ write "app/actions/profile/show.rb", <<~RUBY
50
+ module TestApp
51
+ module Actions
52
+ module Profile
53
+ class Show < TestApp::Action
54
+ def handle(req, res)
55
+ res[:favorite_number] = 123
56
+ end
57
+
58
+ def auto_render?(_res)
59
+ false
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ RUBY
66
+
67
+ write "app/views/profile/show.rb", <<~RUBY
68
+ module TestApp
69
+ module Views
70
+ module Profile
71
+ class Show < TestApp::View
72
+ expose :name, :favorite_number
73
+ end
74
+ end
75
+ end
76
+ end
77
+ RUBY
78
+
79
+ write "app/templates/profile/show.html.slim", <<~'SLIM'
80
+ h1 Hello, #{name}. Your favorite number is #{favorite_number}, right?
81
+ SLIM
82
+
83
+ require "hanami/prepare"
84
+
85
+ action = TestApp::App["actions.profile.show"]
86
+ response = action.(name: "Jennifer")
87
+
88
+ expect(response.body).to eq []
89
+ expect(response.status).to eq 200
90
+ end
91
+ end
92
+
93
+ it "Doesn't render view automatically when body is already assigned" do
94
+ within_app do
95
+ write "app/actions/profile/show.rb", <<~RUBY
96
+ module TestApp
97
+ module Actions
98
+ module Profile
99
+ class Show < TestApp::Action
100
+ def handle(req, res)
101
+ res.body = "200: Okay okay okay"
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ RUBY
108
+
109
+ write "app/views/profile/show.rb", <<~RUBY
110
+ module TestApp
111
+ module Views
112
+ module Profile
113
+ class Show < TestApp::View
114
+ expose :name, :favorite_number
115
+ end
116
+ end
117
+ end
118
+ end
119
+ RUBY
120
+
121
+ write "app/templates/profile/show.html.slim", <<~'SLIM'
122
+ h1 Hello, #{name}. Your favorite number is #{favorite_number}, right?
123
+ SLIM
124
+
125
+ require "hanami/prepare"
126
+
127
+ action = TestApp::App["actions.profile.show"]
128
+ response = action.(name: "Jennifer")
129
+ rendered = response.body[0]
130
+
131
+ expect(rendered).to eq "200: Okay okay okay"
132
+ expect(response.status).to eq 200
133
+ end
134
+ end
135
+
136
+ it "Doesn't render view automatically when halt is called" do
137
+ within_app do
138
+ write "app/actions/profile/show.rb", <<~RUBY
139
+ module TestApp
140
+ module Actions
141
+ module Profile
142
+ class Show < TestApp::Action
143
+ def handle(req, res)
144
+ halt 404
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ RUBY
151
+
152
+ write "app/views/profile/show.rb", <<~RUBY
153
+ module TestApp
154
+ module Views
155
+ module Profile
156
+ class Show < TestApp::View
157
+ expose :name
158
+ end
159
+ end
160
+ end
161
+ end
162
+ RUBY
163
+
164
+ # This template will crash if not rendered with a valid `name` string. The absence
165
+ # of a crash here tells us that the view was never rendered.
166
+ write "app/templates/profile/show.html.slim", <<~'SLIM'
167
+ h1 Hello, #{name.to_str}!
168
+ SLIM
169
+
170
+ require "hanami/prepare"
171
+
172
+ # Call the action without a `name` param, thereby ensuring the view will raise an
173
+ # error if rendered
174
+ action = TestApp::App["actions.profile.show"]
175
+ response = action.({})
176
+ rendered = response.body[0]
177
+
178
+ aggregate_failures do
179
+ expect(rendered).to eq "Not Found"
180
+ expect(response.status).to eq 404
181
+ end
182
+ end
183
+ end
184
+
185
+ it "Does not render if no view is available" do
186
+ within_app do
187
+ write "app/actions/profile/show.rb", <<~RUBY
188
+ module TestApp
189
+ module Actions
190
+ module Profile
191
+ class Show < TestApp::Action
192
+ end
193
+ end
194
+ end
195
+ end
196
+ RUBY
197
+
198
+ require "hanami/prepare"
199
+
200
+ action = TestApp::App["actions.profile.show"]
201
+ response = action.({})
202
+ expect(response.body).to eq []
203
+ expect(response.status).to eq 200
204
+ end
205
+ end
206
+
207
+ def within_app
208
+ with_tmp_directory(Dir.mktmpdir) do
209
+ write "config/app.rb", <<~RUBY
210
+ require "hanami"
211
+
212
+ module TestApp
213
+ class App < Hanami::App
214
+ end
215
+ end
216
+ RUBY
217
+
218
+ write "app/action.rb", <<~RUBY
219
+ # auto_register: false
220
+
221
+ module TestApp
222
+ class Action < Hanami::Action
223
+ end
224
+ end
225
+ RUBY
226
+
227
+ write "app/view.rb", <<~RUBY
228
+ # auto_register: false
229
+
230
+ require "hanami/view"
231
+
232
+ module TestApp
233
+ class View < Hanami::View
234
+ end
235
+ end
236
+ RUBY
237
+
238
+ write "app/templates/layouts/app.html.slim", <<~SLIM
239
+ html
240
+ body
241
+ == yield
242
+ SLIM
243
+
244
+ yield
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami"
4
+
5
+ RSpec.describe "App action / View rendering / Paired view inference", :app_integration do
6
+ before do
7
+ module TestApp
8
+ class App < Hanami::App
9
+ end
10
+ end
11
+
12
+ Hanami.app.prepare
13
+
14
+ module TestApp
15
+ class Action < Hanami::Action
16
+ end
17
+ end
18
+ end
19
+
20
+ let(:action) { action_class.new }
21
+
22
+ describe "Ordinary action" do
23
+ before do
24
+ module TestApp
25
+ module Actions
26
+ module Articles
27
+ class Index < TestApp::Action
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ let(:action_class) { TestApp::Actions::Articles::Index }
35
+
36
+ context "Paired view exists" do
37
+ before do
38
+ TestApp::App.register "views.articles.index", view
39
+ end
40
+
41
+ let(:view) { double(:view) }
42
+
43
+ it "auto-injects a paired view from a matching container identifier" do
44
+ expect(action.view).to be view
45
+ end
46
+
47
+ context "Another view explicitly auto-injected" do
48
+ before do
49
+ module TestApp
50
+ module Actions
51
+ module Articles
52
+ class Index < TestApp::Action
53
+ include Deps[view: "views.articles.custom"]
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ TestApp::App.register "views.articles.custom", explicit_view
60
+ end
61
+
62
+ let(:action_class) { TestApp::Actions::Articles::Index }
63
+ let(:explicit_view) { double(:explicit_view) }
64
+
65
+ it "respects the explicitly auto-injected view" do
66
+ expect(action.view).to be explicit_view
67
+ end
68
+ end
69
+ end
70
+
71
+ context "No paired view exists" do
72
+ it "does not auto-inject any view" do
73
+ expect(action.view).to be_nil
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "RESTful action" do
79
+ before do
80
+ module TestApp
81
+ module Actions
82
+ module Articles
83
+ class Create < TestApp::Action
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ let(:action_class) { TestApp::Actions::Articles::Create }
91
+ let(:direct_paired_view) { double(:direct_paired_view) }
92
+ let(:alternative_paired_view) { double(:alternative_paired_view) }
93
+
94
+ context "Direct paired view exists" do
95
+ before do
96
+ TestApp::App.register "views.articles.create", direct_paired_view
97
+ TestApp::App.register "views.articles.new", alternative_paired_view
98
+ end
99
+
100
+ it "auto-injects the directly paired view" do
101
+ expect(action.view).to be direct_paired_view
102
+ end
103
+ end
104
+
105
+ context "Alternative paired view exists" do
106
+ before do
107
+ TestApp::App.register "views.articles.new", alternative_paired_view
108
+ end
109
+
110
+ it "auto-injects the alternative paired view" do
111
+ expect(action.view).to be alternative_paired_view
112
+ end
113
+ end
114
+ end
115
+ end