hanami 2.0.0.beta1.1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -0
  3. data/hanami.gemspec +1 -2
  4. data/lib/hanami/app.rb +76 -16
  5. data/lib/hanami/assets/{application_configuration.rb → app_configuration.rb} +1 -1
  6. data/lib/hanami/configuration.rb +20 -20
  7. data/lib/hanami/extensions/action/slice_configured_action.rb +44 -1
  8. data/lib/hanami/extensions/view/slice_configured_view.rb +47 -7
  9. data/lib/hanami/providers/rack.rb +2 -0
  10. data/lib/hanami/providers/settings.rb +81 -6
  11. data/lib/hanami/settings/env_store.rb +32 -0
  12. data/lib/hanami/settings.rb +8 -12
  13. data/lib/hanami/setup.rb +1 -6
  14. data/lib/hanami/slice/routing/middleware/stack.rb +26 -5
  15. data/lib/hanami/slice.rb +38 -45
  16. data/lib/hanami/slice_configurable.rb +14 -1
  17. data/lib/hanami/slice_registrar.rb +65 -5
  18. data/lib/hanami/version.rb +1 -1
  19. data/lib/hanami.rb +53 -2
  20. data/spec/new_integration/action/slice_configuration_spec.rb +287 -0
  21. data/spec/new_integration/code_loading/loading_from_lib_spec.rb +208 -0
  22. data/spec/new_integration/dotenv_loading_spec.rb +137 -0
  23. data/spec/new_integration/settings/access_to_constants_spec.rb +169 -0
  24. data/spec/new_integration/settings/loading_from_env_spec.rb +187 -0
  25. data/spec/new_integration/settings/settings_component_loading_spec.rb +113 -0
  26. data/spec/new_integration/settings/using_types_spec.rb +87 -0
  27. data/spec/new_integration/setup_spec.rb +145 -0
  28. data/spec/new_integration/slices/slice_loading_spec.rb +171 -0
  29. data/spec/new_integration/view/context/settings_spec.rb +5 -1
  30. data/spec/new_integration/view/slice_configuration_spec.rb +289 -0
  31. data/spec/support/app_integration.rb +4 -5
  32. data/spec/unit/hanami/configuration/slices_spec.rb +34 -0
  33. data/spec/unit/hanami/settings/env_store_spec.rb +52 -0
  34. data/spec/unit/hanami/slice_configurable_spec.rb +2 -2
  35. data/spec/unit/hanami/version_spec.rb +1 -1
  36. metadata +30 -28
  37. data/lib/hanami/settings/dotenv_store.rb +0 -58
  38. data/spec/new_integration/action/configuration_spec.rb +0 -26
  39. data/spec/new_integration/settings_spec.rb +0 -115
  40. data/spec/new_integration/view/configuration_spec.rb +0 -49
  41. data/spec/unit/hanami/settings/dotenv_store_spec.rb +0 -119
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/test"
4
+
5
+ RSpec.describe "Slices / Slice loading", :app_integration, :aggregate_failures do
6
+ let(:app_modules) { %i[TestApp Admin Editorial Main Shop] }
7
+
8
+ describe "loading specific slices with config.slices" do
9
+ describe "setup app" do
10
+ it "ignores any explicitly registered slices not included in load_slices" do
11
+ with_tmp_directory(Dir.mktmpdir) do
12
+ write "config/app.rb", <<~'RUBY'
13
+ require "hanami"
14
+
15
+ module TestApp
16
+ class App < Hanami::App
17
+ config.slices = %w[admin]
18
+ end
19
+ end
20
+ RUBY
21
+
22
+ require "hanami/setup"
23
+
24
+ expect { Hanami.app.register_slice :main }.not_to(change { Hanami.app.slices.keys })
25
+ expect { Main }.to raise_error(NameError)
26
+
27
+ expect { Hanami.app.register_slice :admin }.to change { Hanami.app.slices.keys }.to [:admin]
28
+ expect(Admin::Slice).to be
29
+ end
30
+ end
31
+
32
+ describe "nested slices" do
33
+ it "ignores any explicitly registered slices not included in load_slices" do
34
+ with_tmp_directory(Dir.mktmpdir) do
35
+ write "config/app.rb", <<~'RUBY'
36
+ require "hanami"
37
+
38
+ module TestApp
39
+ class App < Hanami::App
40
+ config.slices = %w[admin.shop]
41
+ end
42
+ end
43
+ RUBY
44
+
45
+ require "hanami/setup"
46
+
47
+ expect { Hanami.app.register_slice :admin }.to change { Hanami.app.slices.keys }.to [:admin]
48
+
49
+ expect { Admin::Slice.register_slice :editorial }.not_to(change { Admin::Slice.slices.keys })
50
+ expect { Admin::Slice.register_slice :shop }.to change { Admin::Slice.slices.keys }.to [:shop]
51
+ expect(Shop::Slice).to be
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "prepared app" do
58
+ it "loads only the slices included in load_slices" do
59
+ with_tmp_directory(Dir.mktmpdir) do
60
+ write "config/app.rb", <<~'RUBY'
61
+ require "hanami"
62
+
63
+ module TestApp
64
+ class App < Hanami::App
65
+ config.slices = %w[admin]
66
+ end
67
+ end
68
+ RUBY
69
+
70
+ write "slices/admin/.keep"
71
+ write "slices/main/.keep"
72
+
73
+ require "hanami/prepare"
74
+
75
+ expect(Hanami.app.slices.keys).to eq [:admin]
76
+ expect(Admin::Slice).to be
77
+ expect { Main }.to raise_error(NameError)
78
+ end
79
+ end
80
+
81
+ it "ignores unknown slices in load_slices" do
82
+ with_tmp_directory(Dir.mktmpdir) do
83
+ write "config/app.rb", <<~'RUBY'
84
+ require "hanami"
85
+
86
+ module TestApp
87
+ class App < Hanami::App
88
+ config.slices = %w[admin meep morp]
89
+ end
90
+ end
91
+ RUBY
92
+
93
+ write "slices/admin/.keep"
94
+ write "slices/main/.keep"
95
+
96
+ require "hanami/prepare"
97
+
98
+ expect(Hanami.app.slices.keys).to eq [:admin]
99
+ end
100
+ end
101
+
102
+ describe "nested slices" do
103
+ it "loads only the dot-delimited nested slices (and their parents) included in load_slices" do
104
+ with_tmp_directory(Dir.mktmpdir) do
105
+ write "config/app.rb", <<~'RUBY'
106
+ require "hanami"
107
+
108
+ module TestApp
109
+ class App < Hanami::App
110
+ config.slices = %w[admin.shop]
111
+ end
112
+ end
113
+ RUBY
114
+
115
+ write "slices/admin/.keep"
116
+ write "slices/admin/slices/shop/.keep"
117
+ write "slices/admin/slices/editorial/.keep"
118
+ write "slices/main/.keep"
119
+
120
+ require "hanami/prepare"
121
+
122
+ expect(Hanami.app.slices.keys).to eq [:admin]
123
+ expect(Admin::Slice.slices.keys).to eq [:shop]
124
+
125
+ expect(Admin::Slice).to be
126
+ expect(Shop::Slice).to be
127
+
128
+ expect { Editorial }.to raise_error(NameError)
129
+ expect { Main }.to raise_error(NameError)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ describe "using ENV vars" do
137
+ before do
138
+ @orig_env = ENV.to_h
139
+ end
140
+
141
+ after do
142
+ ENV.replace(@orig_env)
143
+ end
144
+
145
+ it "uses HANAMI_SLICES" do
146
+ ENV["HANAMI_SLICES"] = "admin"
147
+
148
+ with_tmp_directory(Dir.mktmpdir) do
149
+ write "config/app.rb", <<~'RUBY'
150
+ require "hanami"
151
+
152
+ module TestApp
153
+ class App < Hanami::App
154
+ end
155
+ end
156
+ RUBY
157
+
158
+ write "slices/admin/.keep"
159
+ write "slices/main/.keep"
160
+
161
+ require "hanami/setup"
162
+
163
+ expect(Hanami.app.config.slices).to eq %w[admin]
164
+
165
+ require "hanami/prepare"
166
+
167
+ expect(Hanami.app.slices.keys).to eq [:admin]
168
+ end
169
+ end
170
+ end
171
+ end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami"
4
+ require "hanami/settings"
4
5
 
5
6
  RSpec.describe "App view / Context / Settings", :app_integration do
6
7
  before do
7
8
  module TestApp
8
9
  class App < Hanami::App
9
10
  end
11
+
12
+ class Settings < Hanami::Settings
13
+ end
10
14
  end
11
15
 
12
16
  Hanami.prepare
@@ -24,7 +28,7 @@ RSpec.describe "App view / Context / Settings", :app_integration do
24
28
 
25
29
  describe "#settings" do
26
30
  it "is the app settings by default" do
27
- expect(context.settings).to be TestApp::App.settings
31
+ expect(context.settings).to be TestApp::App["settings"]
28
32
  end
29
33
 
30
34
  context "injected settings" do
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App view / Slice configuration", :app_integration do
4
+ before do
5
+ with_directory(@dir = make_tmp_directory) do
6
+ write "config/app.rb", <<~'RUBY'
7
+ require "hanami"
8
+
9
+ module TestApp
10
+ class App < Hanami::App
11
+ end
12
+ end
13
+ RUBY
14
+
15
+ write "app/view.rb", <<~'RUBY'
16
+ require "hanami/view"
17
+
18
+ module TestApp
19
+ class View < Hanami::View
20
+ end
21
+ end
22
+ RUBY
23
+
24
+ require "hanami/setup"
25
+ end
26
+ end
27
+
28
+ def prepare_app
29
+ with_directory(@dir) { require "hanami/prepare" }
30
+ end
31
+
32
+ describe "inheriting from app-level base class" do
33
+ describe "app-level base class" do
34
+ it "applies default views config from the app", :aggregate_failures do
35
+ prepare_app
36
+
37
+ expect(TestApp::View.config.layouts_dir).to eq "layouts"
38
+ expect(TestApp::View.config.layout).to eq "app"
39
+ end
40
+
41
+ it "applies views config from the app" do
42
+ Hanami.app.config.views.layout = "app_layout"
43
+
44
+ prepare_app
45
+
46
+ expect(TestApp::View.config.layout).to eq "app_layout"
47
+ end
48
+
49
+ it "does not override config in the base class" do
50
+ Hanami.app.config.views.layout = "app_layout"
51
+
52
+ prepare_app
53
+
54
+ TestApp::View.config.layout = "custom_layout"
55
+
56
+ expect(TestApp::View.config.layout).to eq "custom_layout"
57
+ end
58
+ end
59
+
60
+ describe "subclass in app" do
61
+ before do
62
+ with_directory(@dir) do
63
+ write "app/views/articles/index.rb", <<~'RUBY'
64
+ module TestApp
65
+ module Views
66
+ module Articles
67
+ class Index < TestApp::View
68
+ end
69
+ end
70
+ end
71
+ end
72
+ RUBY
73
+ end
74
+ end
75
+
76
+ it "applies default views config from the app", :aggregate_failures do
77
+ prepare_app
78
+
79
+ expect(TestApp::Views::Articles::Index.config.layouts_dir).to eq "layouts"
80
+ expect(TestApp::Views::Articles::Index.config.layout).to eq "app"
81
+ end
82
+
83
+ it "applies views config from the app" do
84
+ Hanami.app.config.views.layout = "app_layout"
85
+
86
+ prepare_app
87
+
88
+ expect(TestApp::Views::Articles::Index.config.layout).to eq "app_layout"
89
+ end
90
+
91
+ it "applies config from the base class" do
92
+ prepare_app
93
+
94
+ TestApp::View.config.layout = "base_class_layout"
95
+
96
+ expect(TestApp::Views::Articles::Index.config.layout).to eq "base_class_layout"
97
+ end
98
+ end
99
+
100
+ describe "subclass in slice" do
101
+ before do
102
+ with_directory(@dir) do
103
+ write "slices/admin/views/articles/index.rb", <<~'RUBY'
104
+ module Admin
105
+ module Views
106
+ module Articles
107
+ class Index < TestApp::View
108
+ end
109
+ end
110
+ end
111
+ end
112
+ RUBY
113
+ end
114
+ end
115
+
116
+ it "applies default views config from the app", :aggregate_failures do
117
+ prepare_app
118
+
119
+ expect(Admin::Views::Articles::Index.config.layouts_dir).to eq "layouts"
120
+ expect(Admin::Views::Articles::Index.config.layout).to eq "app"
121
+ end
122
+
123
+ it "applies views config from the app" do
124
+ Hanami.app.config.views.layout = "app_layout"
125
+
126
+ prepare_app
127
+
128
+ expect(Admin::Views::Articles::Index.config.layout).to eq "app_layout"
129
+ end
130
+
131
+ it "applies config from the base class" do
132
+ prepare_app
133
+
134
+ TestApp::View.config.layout = "base_class_layout"
135
+
136
+ expect(Admin::Views::Articles::Index.config.layout).to eq "base_class_layout"
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "inheriting from a slice-level base class, in turn inheriting from an app-level base class" do
142
+ before do
143
+ with_directory(@dir) do
144
+ write "slices/admin/view.rb", <<~'RUBY'
145
+ module Admin
146
+ class View < TestApp::View
147
+ end
148
+ end
149
+ RUBY
150
+ end
151
+ end
152
+
153
+ describe "slice-level base class" do
154
+ it "applies default views config from the app", :aggregate_failures do
155
+ prepare_app
156
+
157
+ expect(Admin::View.config.layouts_dir).to eq "layouts"
158
+ expect(Admin::View.config.layout).to eq "app"
159
+ end
160
+
161
+ it "applies views config from the app" do
162
+ Hanami.app.config.views.layout = "app_layout"
163
+
164
+ prepare_app
165
+
166
+ expect(Admin::View.config.layout).to eq "app_layout"
167
+ end
168
+
169
+ it "applies config from the app base class" do
170
+ prepare_app
171
+
172
+ TestApp::View.config.layout = "app_base_class_layout"
173
+
174
+ expect(Admin::View.config.layout).to eq "app_base_class_layout"
175
+ end
176
+
177
+ context "slice views config present" do
178
+ before do
179
+ with_directory(@dir) do
180
+ write "config/slices/admin.rb", <<~'RUBY'
181
+ module Admin
182
+ class Slice < Hanami::Slice
183
+ config.views.layout = "slice_layout"
184
+ end
185
+ end
186
+ RUBY
187
+ end
188
+ end
189
+
190
+ it "applies views config from the slice" do
191
+ prepare_app
192
+
193
+ expect(Admin::View.config.layout).to eq "slice_layout"
194
+ end
195
+
196
+ it "prefers views config from the slice over config from the app-level base class" do
197
+ prepare_app
198
+
199
+ TestApp::View.config.layout = "app_base_class_layout"
200
+
201
+ expect(Admin::View.config.layout).to eq "slice_layout"
202
+ end
203
+
204
+ it "prefers config from the base class over views config from the slice" do
205
+ prepare_app
206
+
207
+ TestApp::View.config.layout = "app_base_class_layout"
208
+ Admin::View.config.layout = "slice_base_class_layout"
209
+
210
+ expect(Admin::View.config.layout).to eq "slice_base_class_layout"
211
+ end
212
+ end
213
+ end
214
+
215
+ describe "subclass in slice" do
216
+ before do
217
+ with_directory(@dir) do
218
+ write "slices/admin/views/articles/index.rb", <<~'RUBY'
219
+ module Admin
220
+ module Views
221
+ module Articles
222
+ class Index < Admin::View
223
+ end
224
+ end
225
+ end
226
+ end
227
+ RUBY
228
+ end
229
+ end
230
+
231
+ it "applies default views config from the app", :aggregate_failures do
232
+ prepare_app
233
+
234
+ expect(Admin::Views::Articles::Index.config.layouts_dir).to eq "layouts"
235
+ expect(Admin::Views::Articles::Index.config.layout).to eq "app"
236
+ end
237
+
238
+ it "applies views config from the app" do
239
+ Hanami.app.config.views.layout = "app_layout"
240
+
241
+ prepare_app
242
+
243
+ expect(Admin::Views::Articles::Index.config.layout).to eq "app_layout"
244
+ end
245
+
246
+ it "applies views config from the slice" do
247
+ with_directory(@dir) do
248
+ write "config/slices/admin.rb", <<~'RUBY'
249
+ module Admin
250
+ class Slice < Hanami::Slice
251
+ config.views.layout = "slice_layout"
252
+ end
253
+ end
254
+ RUBY
255
+ end
256
+
257
+ prepare_app
258
+
259
+ expect(Admin::Views::Articles::Index.config.layout).to eq "slice_layout"
260
+ end
261
+
262
+ it "applies config from the slice base class" do
263
+ prepare_app
264
+
265
+ Admin::View.config.layout = "slice_base_class_layout"
266
+
267
+ expect(Admin::Views::Articles::Index.config.layout).to eq "slice_base_class_layout"
268
+ end
269
+
270
+ it "prefers config from the slice base class over views config from the slice" do
271
+ with_directory(@dir) do
272
+ write "config/slices/admin.rb", <<~'RUBY'
273
+ module Admin
274
+ class Slice < Hanami::Slice
275
+ config.views.layout = "slice_layout"
276
+ end
277
+ end
278
+ RUBY
279
+ end
280
+
281
+ prepare_app
282
+
283
+ Admin::View.config.layout = "slice_base_class_layout"
284
+
285
+ expect(Admin::Views::Articles::Index.config.layout).to eq "slice_base_class_layout"
286
+ end
287
+ end
288
+ end
289
+ end
@@ -20,7 +20,7 @@ module RSpec
20
20
  end
21
21
 
22
22
  RSpec.shared_context "Application integration" do
23
- let(:application_modules) { %i[TestApp Admin Main Search] }
23
+ let(:app_modules) { %i[TestApp Admin Main Search] }
24
24
  end
25
25
 
26
26
  RSpec.configure do |config|
@@ -52,9 +52,8 @@ RSpec.configure do |config|
52
52
  Zeitwerk::ExplicitNamespace.cpaths.clear
53
53
  Zeitwerk::ExplicitNamespace.tracer.disable
54
54
 
55
- if Hanami.instance_variable_defined?(:@_app)
56
- Hanami.remove_instance_variable(:@_app)
57
- end
55
+ Hanami.instance_variable_set(:@_bundled, {})
56
+ Hanami.remove_instance_variable(:@_app) if Hanami.instance_variable_defined?(:@_app)
58
57
 
59
58
  $LOAD_PATH.replace(@load_paths)
60
59
 
@@ -68,7 +67,7 @@ RSpec.configure do |config|
68
67
  }
69
68
  $LOADED_FEATURES.replace(@loaded_features + new_features_to_keep)
70
69
 
71
- application_modules.each do |app_module_name|
70
+ app_modules.each do |app_module_name|
72
71
  next unless Object.const_defined?(app_module_name)
73
72
 
74
73
  Object.const_get(app_module_name).tap do |mod|
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+ require "hanami/configuration"
5
+ require "hanami/slice_name"
6
+
7
+ RSpec.describe Hanami::Configuration, "#slices" do
8
+ subject(:config) { described_class.new(app_name: app_name, env: :development) }
9
+ let(:app_name) { Hanami::SliceName.new(double(name: "MyApp::App"), inflector: Dry::Inflector.new) }
10
+
11
+ subject(:slices) { config.slices }
12
+
13
+ before do
14
+ @orig_env = ENV.to_h
15
+ end
16
+
17
+ after do
18
+ ENV.replace(@orig_env)
19
+ end
20
+
21
+ it "is nil by default" do
22
+ is_expected.to be nil
23
+ end
24
+
25
+ it "defaults to the HANAMI_LOAD_SLICES env var, separated by commas" do
26
+ ENV["HANAMI_SLICES"] = "main,admin"
27
+ is_expected.to eq %w[main admin]
28
+ end
29
+
30
+ it "strips spaces from HANAMI_LOAD_SLICES env var entries" do
31
+ ENV["HANAMI_SLICES"] = "main, admin"
32
+ is_expected.to eq %w[main admin]
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/settings/env_store"
4
+
5
+ RSpec.describe Hanami::Settings::EnvStore do
6
+ it "defaults to using ENV as the store" do
7
+ orig_env = ENV.to_h
8
+
9
+ ENV["FOO"] = "bar"
10
+ expect(described_class.new.fetch("FOO")).to eq "bar"
11
+
12
+ ENV.replace(orig_env)
13
+ end
14
+
15
+ describe "#fetch" do
16
+ it "fetches from ENV" do
17
+ store = described_class.new(store: {"FOO" => "bar"})
18
+
19
+ expect(store.fetch("FOO")).to eq("bar")
20
+ end
21
+
22
+ it "capitalizes name" do
23
+ store = described_class.new(store: {"FOO" => "bar"})
24
+
25
+ expect(store.fetch("foo")).to eq("bar")
26
+ end
27
+
28
+ it "coerces name to string" do
29
+ store = described_class.new(store: {"FOO" => "bar"})
30
+
31
+ expect(store.fetch(:foo)).to eq("bar")
32
+ end
33
+
34
+ it "returns default when value is not found" do
35
+ store = described_class.new(store: {"FOO" => "bar"})
36
+
37
+ expect(store.fetch("BAZ", "qux")).to eq("qux")
38
+ end
39
+
40
+ it "returns the block execution when value is not found" do
41
+ store = described_class.new(store: {"FOO" => "bar"})
42
+
43
+ expect(store.fetch("BAZ") { "qux" }).to eq("qux") # rubocop:disable Style/RedundantFetchBlock
44
+ end
45
+
46
+ it "raises KeyError when value is not found and no default is given" do
47
+ store = described_class.new(store: {"FOO" => "bar"})
48
+
49
+ expect { store.fetch("BAZ") }.to raise_error(KeyError)
50
+ end
51
+ end
52
+ end
@@ -54,8 +54,8 @@ RSpec.describe Hanami::SliceConfigurable, :app_integration do
54
54
 
55
55
  subject(:subclass) { Main::MySubSubclass }
56
56
 
57
- it "calls `configure_for_slice` again for the same slice" do
58
- expect(subclass.traces).to eq [Main::Slice, Main::Slice]
57
+ it "does not call `configure_for_slice` again" do
58
+ expect(subclass.traces).to eq [Main::Slice]
59
59
  end
60
60
  end
61
61
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  RSpec.describe "Hanami::VERSION" do
4
4
  it "returns current version" do
5
- expect(Hanami::VERSION).to eq("2.0.0.beta1.1")
5
+ expect(Hanami::VERSION).to eq("2.0.0.beta2")
6
6
  end
7
7
  end