alchemy_cms 8.0.0.b → 8.0.0.c

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/alchemy/admin.css +1 -1
  3. data/app/assets/images/alchemy/element_icons/layout-bottom-2-line.svg +1 -0
  4. data/app/components/alchemy/admin/link_dialog/tabs.rb +1 -1
  5. data/app/components/alchemy/admin/locale_select.rb +38 -0
  6. data/app/controllers/alchemy/admin/pages_controller.rb +1 -1
  7. data/app/controllers/alchemy/admin/pictures_controller.rb +11 -5
  8. data/app/controllers/alchemy/admin/resources_controller.rb +1 -1
  9. data/app/decorators/alchemy/ingredient_editor.rb +9 -1
  10. data/app/helpers/alchemy/admin/base_helper.rb +0 -7
  11. data/app/helpers/alchemy/admin/form_helper.rb +2 -1
  12. data/app/models/alchemy/element_definition.rb +3 -2
  13. data/app/models/alchemy/ingredients/boolean.rb +2 -1
  14. data/app/models/alchemy/page/publisher.rb +1 -1
  15. data/app/models/alchemy/resource.rb +15 -2
  16. data/app/models/alchemy/storage_adapter/dragonfly.rb +2 -2
  17. data/app/stylesheets/alchemy/admin/dashboard.scss +13 -0
  18. data/app/stylesheets/alchemy/admin/elements.scss +1 -1
  19. data/app/stylesheets/alchemy/admin/nodes.scss +6 -2
  20. data/app/stylesheets/alchemy/admin/sitemap.scss +6 -17
  21. data/app/views/alchemy/admin/dashboard/index.html.erb +1 -1
  22. data/app/views/alchemy/admin/dashboard/info.html.erb +36 -6
  23. data/app/views/alchemy/admin/elements/_header.html.erb +8 -9
  24. data/app/views/alchemy/admin/nodes/_form.html.erb +5 -1
  25. data/app/views/alchemy/admin/pictures/_archive.html.erb +1 -1
  26. data/app/views/alchemy/admin/pictures/_form.html.erb +10 -5
  27. data/app/views/alchemy/admin/pictures/_picture.html.erb +15 -10
  28. data/app/views/alchemy/admin/pictures/show.html.erb +11 -13
  29. data/app/views/alchemy/admin/pictures/update.turbo_stream.erb +1 -1
  30. data/app/views/alchemy/admin/uploader/_button.html.erb +1 -1
  31. data/app/views/layouts/alchemy/admin.html.erb +3 -6
  32. data/lib/alchemy/configuration/base_option.rb +18 -5
  33. data/lib/alchemy/configuration/boolean_option.rb +2 -5
  34. data/lib/alchemy/configuration/collection_option.rb +69 -0
  35. data/lib/alchemy/configuration/configuration_option.rb +35 -0
  36. data/lib/alchemy/configuration/pathname_option.rb +12 -0
  37. data/lib/alchemy/configuration.rb +44 -6
  38. data/lib/alchemy/configurations/importmap.rb +11 -0
  39. data/lib/alchemy/configurations/mailer.rb +2 -2
  40. data/lib/alchemy/configurations/main.rb +141 -3
  41. data/lib/alchemy/configurations/uploader.rb +2 -2
  42. data/lib/alchemy/deprecation.rb +1 -1
  43. data/lib/alchemy/engine.rb +27 -14
  44. data/lib/alchemy/test_support/config_stubbing.rb +13 -4
  45. data/lib/alchemy/test_support/factories/language_factory.rb +8 -4
  46. data/lib/alchemy/test_support/factories/page_factory.rb +1 -0
  47. data/lib/alchemy/version.rb +1 -1
  48. data/lib/alchemy.rb +16 -160
  49. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +78 -7
  50. data/lib/tasks/alchemy/assets.rake +1 -1
  51. metadata +7 -6
  52. data/app/assets/images/alchemy/element_icons/default.svg +0 -1
  53. data/lib/alchemy/configuration/class_set_option.rb +0 -46
  54. data/lib/alchemy/configuration/integer_list_option.rb +0 -13
  55. data/lib/alchemy/configuration/list_option.rb +0 -22
  56. data/lib/alchemy/configuration/string_list_option.rb +0 -13
@@ -9,15 +9,28 @@ module Alchemy
9
9
 
10
10
  def initialize(value:, name:, **args)
11
11
  @name = name
12
- @value = validate(value) unless value.nil?
12
+ validate(value) unless value.nil?
13
+ @value = value
13
14
  end
14
15
  attr_reader :name, :value
15
16
 
16
- private
17
-
18
17
  def validate(value)
19
- raise TypeError, "#{name} must be set as a #{self.class.value_class.name}, given #{value.inspect}" unless value.is_a?(self.class.value_class)
20
- value
18
+ raise ConfigurationError.new(name, value, allowed_classes) unless allowed_classes.any? { value.is_a?(_1) }
19
+ end
20
+
21
+ def allowed_classes
22
+ [self.class.value_class]
23
+ end
24
+
25
+ def raw_value = @value
26
+
27
+ def ==(other)
28
+ self.class == other.class && raw_value == other.raw_value
29
+ end
30
+ alias_method :eql?, :==
31
+
32
+ def hash
33
+ [self.class, raw_value].hash
21
34
  end
22
35
  end
23
36
  end
@@ -5,11 +5,8 @@ require "alchemy/configuration/base_option"
5
5
  module Alchemy
6
6
  class Configuration
7
7
  class BooleanOption < BaseOption
8
- private
9
-
10
- def validate(value)
11
- raise TypeError, "#{name} must be a Boolean, given #{value.inspect}" unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
12
- value
8
+ def allowed_classes
9
+ [TrueClass, FalseClass]
13
10
  end
14
11
  end
15
12
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/base_option"
4
+
5
+ module Alchemy
6
+ class Configuration
7
+ class CollectionOption < BaseOption
8
+ include Enumerable
9
+
10
+ def self.value_class
11
+ Enumerable
12
+ end
13
+
14
+ attr_reader :collection_class, :item_class, :item_args
15
+
16
+ def initialize(value:, name:, item_type:, collection_class: Array, **args)
17
+ @collection_class = collection_class
18
+ @item_class = get_item_class(item_type)
19
+ @item_args = args
20
+ value = [] if value.nil?
21
+ collection = @collection_class.new(value.map { |value| to_item(value) })
22
+ super(value: collection, name: name)
23
+ rescue ConfigurationError => configuration_error
24
+ raise ConfigurationError.new(name, configuration_error.value, configuration_error.allowed_classes)
25
+ end
26
+
27
+ def value
28
+ self
29
+ end
30
+
31
+ def <<(value)
32
+ @value << to_item(value)
33
+ end
34
+ alias_method(:add, :<<)
35
+
36
+ def concat(values)
37
+ values.each do |value|
38
+ add(value)
39
+ end
40
+ end
41
+
42
+ delegate :join, :[], to: :to_a
43
+
44
+ delegate :clear, :empty?, to: :@value
45
+
46
+ def each(&block)
47
+ @value.each do |option|
48
+ yield option.value
49
+ end
50
+ end
51
+
52
+ def to_serializable_array
53
+ to_a.map do |item|
54
+ item.respond_to?(:to_h) ? item.to_h : item
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def to_item(value)
61
+ @item_class.new(value: value, name: "#{name}_item", **item_args)
62
+ end
63
+
64
+ def get_item_class(item_type)
65
+ "Alchemy::Configuration::#{item_type.to_s.classify}Option".constantize
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/base_option"
4
+
5
+ module Alchemy
6
+ class Configuration
7
+ class ConfigurationOption < BaseOption
8
+ def self.value_class
9
+ Hash
10
+ end
11
+
12
+ attr_reader :config_class
13
+
14
+ def initialize(value:, name:, config_class:, **args)
15
+ @name = name
16
+ @config_class = config_class
17
+ validate(value)
18
+ @value = if value.is_a?(config_class)
19
+ value
20
+ else
21
+ config_class.new(value)
22
+ end
23
+ end
24
+
25
+ def validate(value)
26
+ return true if value.is_a?(config_class)
27
+ super
28
+ end
29
+
30
+ def allowed_classes
31
+ super + [config_class]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "alchemy/configuration/base_option"
4
+ module Alchemy
5
+ class Configuration
6
+ class PathnameOption < BaseOption
7
+ def self.value_class
8
+ Pathname
9
+ end
10
+ end
11
+ end
12
+ end
@@ -4,16 +4,28 @@ require "active_support"
4
4
  require "active_support/core_ext/string"
5
5
 
6
6
  require "alchemy/configuration/boolean_option"
7
+ require "alchemy/configuration/collection_option"
8
+ require "alchemy/configuration/configuration_option"
7
9
  require "alchemy/configuration/class_option"
8
- require "alchemy/configuration/class_set_option"
9
10
  require "alchemy/configuration/integer_option"
10
- require "alchemy/configuration/integer_list_option"
11
+ require "alchemy/configuration/pathname_option"
11
12
  require "alchemy/configuration/regexp_option"
12
- require "alchemy/configuration/string_list_option"
13
13
  require "alchemy/configuration/string_option"
14
14
 
15
15
  module Alchemy
16
16
  class Configuration
17
+ class ConfigurationError < StandardError
18
+ attr_reader :name, :value, :allowed_classes
19
+
20
+ def initialize(name, value, allowed_classes)
21
+ @name = name
22
+ @value = value
23
+ @allowed_classes = allowed_classes
24
+ expected_classes_message = allowed_classes.map(&:name).to_sentence(two_words_connector: " or ", last_word_connector: ", or ")
25
+ super("Invalid configuration value for #{name}: #{value.inspect} (expected #{expected_classes_message})")
26
+ end
27
+ end
28
+
17
29
  def initialize(configuration_hash = {})
18
30
  set(configuration_hash)
19
31
  end
@@ -45,7 +57,8 @@ module Alchemy
45
57
 
46
58
  def to_h
47
59
  self.class.defined_options.map do |option|
48
- [option, send(option)]
60
+ value = send(option)
61
+ [option, value.respond_to?(:to_serializable_array) ? value.to_serializable_array : value]
49
62
  end.concat(
50
63
  self.class.defined_configurations.map do |configuration|
51
64
  [configuration, send(configuration).to_h]
@@ -58,6 +71,10 @@ module Alchemy
58
71
 
59
72
  def defined_options = []
60
73
 
74
+ def defined_values
75
+ defined_options + defined_configurations
76
+ end
77
+
61
78
  def configuration(name, configuration_class)
62
79
  # The defined configurations on a class are all those defined directly on
63
80
  # that class as well as those defined on ancestors.
@@ -99,11 +116,19 @@ module Alchemy
99
116
  super() + singleton_options
100
117
  end
101
118
 
102
- define_method(name) do
119
+ define_method("#{name}_option") do
103
120
  unless instance_variable_defined?(:"@#{name}")
104
121
  send(:"#{name}=", default)
105
122
  end
106
- instance_variable_get(:"@#{name}").value
123
+ instance_variable_get(:"@#{name}")
124
+ end
125
+
126
+ define_method(name) do
127
+ send("#{name}_option").value
128
+ end
129
+
130
+ define_method("raw_#{name}") do
131
+ send("#{name}_option").raw_value
107
132
  end
108
133
 
109
134
  define_method(:"#{name}=") do |value|
@@ -111,5 +136,18 @@ module Alchemy
111
136
  end
112
137
  end
113
138
  end
139
+
140
+ def hash
141
+ self.class.defined_values.map do |ivar|
142
+ [ivar, send(ivar).hash]
143
+ end.hash
144
+ end
145
+
146
+ def ==(other)
147
+ equal?(other) || self.class == other.class && self.class.defined_values.all? do |var|
148
+ send(var) == other.send(var)
149
+ end
150
+ end
151
+ alias_method :eql?, :==
114
152
  end
115
153
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class Importmap < Alchemy::Configuration
6
+ option :importmap_path, :pathname
7
+ option :source_paths, :collection, item_type: :pathname
8
+ option :name, :string
9
+ end
10
+ end
11
+ end
@@ -9,8 +9,8 @@ module Alchemy
9
9
  option :mail_from, :string, default: "your.mail@your-domain.com"
10
10
  option :mail_to, :string, default: "your.mail@your-domain.com"
11
11
  option :subject, :string, default: "A new contact form message"
12
- option :fields, :string_list, default: %w[salutation firstname lastname address zip city phone email message]
13
- option :validate_fields, :string_list, default: %w[lastname email]
12
+ option :fields, :collection, item_type: :string, default: %w[salutation firstname lastname address zip city phone email message]
13
+ option :validate_fields, :collection, item_type: :string, default: %w[lastname email]
14
14
  end
15
15
  end
16
16
  end
@@ -3,6 +3,7 @@
3
3
  require "alchemy/configuration"
4
4
  require "alchemy/configurations/default_language"
5
5
  require "alchemy/configurations/default_site"
6
+ require "alchemy/configurations/importmap"
6
7
  require "alchemy/configurations/format_matchers"
7
8
  require "alchemy/configurations/mailer"
8
9
  require "alchemy/configurations/page_cache"
@@ -162,7 +163,7 @@ module Alchemy
162
163
  # user_roles:
163
164
  # rolename: Name of the role
164
165
  #
165
- option :user_roles, :string_list, default: %w[member author editor admin]
166
+ option :user_roles, :collection, item_type: :string, default: %w[member author editor admin]
166
167
 
167
168
  # === Uploader Settings
168
169
  #
@@ -184,7 +185,7 @@ module Alchemy
184
185
  #
185
186
  # jQuery(a[data-link-target="overlay"]).dialog();
186
187
  #
187
- option :link_target_options, :string_list, default: %w[blank]
188
+ option :link_target_options, :collection, item_type: :string, default: %w[blank]
188
189
 
189
190
  # === Format matchers
190
191
  #
@@ -201,7 +202,7 @@ module Alchemy
201
202
  option :admin_page_preview_layout, :string, default: "application"
202
203
 
203
204
  # The sizes for the preview size select in the page editor.
204
- option :page_preview_sizes, :integer_list, default: [360, 640, 768, 1024, 1280, 1440]
205
+ option :page_preview_sizes, :collection, item_type: :integer, default: [360, 640, 768, 1024, 1280, 1440]
205
206
 
206
207
  # Enable full text search configuration
207
208
  #
@@ -218,6 +219,143 @@ module Alchemy
218
219
  # The storage adapter for Pictures and Attachments
219
220
  #
220
221
  option :storage_adapter, :string, default: "dragonfly"
222
+
223
+ # Define page preview sources
224
+ #
225
+ # A preview source is a Ruby class returning an URL
226
+ # that is used as source for the preview frame in the
227
+ # admin UI.
228
+ #
229
+ # == Example
230
+ #
231
+ # # lib/acme/preview_source.rb
232
+ # class Acme::PreviewSource < Alchemy::Admin::PreviewUrl
233
+ # def url_for(page)
234
+ # if page.site.name == "Next"
235
+ # "https://user:#{ENV['PREVIEW_HTTP_PASS']}@next.acme.com"
236
+ # else
237
+ # "https://www.acme.com"
238
+ # end
239
+ # end
240
+ # end
241
+ #
242
+ # # config/initializers/alchemy.rb
243
+ # require "acme/preview_source"
244
+ # Alchemy.config.preview_sources << "Acme::PreviewSource"
245
+ #
246
+ # # config/locales/de.yml
247
+ # de:
248
+ # activemodel:
249
+ # models:
250
+ # acme/preview_source: Acme Vorschau
251
+ #
252
+ option :preview_sources, :collection, item_type: :class, collection_class: Set, default: ["Alchemy::Admin::PreviewUrl"]
253
+
254
+ # Additional JS modules to be imported in the Alchemy admin UI
255
+ #
256
+ # Be sure to also pin the modules with +Alchemy.importmap+.
257
+ #
258
+ # == Example
259
+ #
260
+ # Alchemy.importmap.pin "flatpickr/de",
261
+ # to: "https://ga.jspm.io/npm:flatpickr@4.6.13/dist/l10n/de.js"
262
+ #
263
+ # Alchemy.config.admin_js_imports << "flatpickr/de"
264
+ #
265
+ option :admin_js_imports, :collection, item_type: :string, collection_class: Set, default: []
266
+
267
+ # Additional importmaps to be included in the Alchemy admin UI
268
+ #
269
+ # Be sure to also pin modules with +Alchemy.importmap+.
270
+ #
271
+ # == Example
272
+ #
273
+ # # config/alchemy/importmap.rb
274
+ # Alchemy.importmap.pin "alchemy_solidus", to: "alchemy_solidus.js", preload: true
275
+ # Alchemy.importmap.pin_all_from Alchemy::Solidus::Engine.root.join("app/javascript/alchemy_solidus"),
276
+ # under: "alchemy_solidus",
277
+ # preload: true
278
+ #
279
+ # # lib/alchemy/solidus/engine.rb
280
+ # initializer "alchemy_solidus.assets", before: "alchemy.importmap" do |app|
281
+ # Alchemy.admin_importmaps.add({
282
+ # importmap_path: root.join("config/importmap.rb"),
283
+ # source_paths: [
284
+ # root.join("app/javascript")
285
+ # ],
286
+ # name: "alchemy_solidus"
287
+ # })
288
+ # app.config.assets.precompile << "alchemy_solidus/manifest.js"
289
+ # end
290
+ #
291
+ option :admin_importmaps, :collection, collection_class: Set, item_type: :configuration, config_class: Alchemy::Configurations::Importmap, default: []
292
+
293
+ # Additional stylesheets to be included in the Alchemy admin UI
294
+ #
295
+ # == Example
296
+ #
297
+ # # lib/alchemy/devise/engine.rb
298
+ # initializer "alchemy.devise.stylesheets", before: "alchemy.admin_stylesheets" do
299
+ # Alchemy.config.admin_stylesheets << "alchemy/devise/admin.css"
300
+ # end
301
+ #
302
+ option :admin_stylesheets, :collection, collection_class: Set, item_type: :string, default: ["alchemy/admin/custom.css"]
303
+
304
+ # Define page publish targets
305
+ #
306
+ # A publish target is a ActiveJob that gets performed
307
+ # whenever a user clicks the publish page button.
308
+ #
309
+ # Use this to trigger deployment hooks of external
310
+ # services in an asychronous way.
311
+ #
312
+ # == Example
313
+ #
314
+ # # app/jobs/publish_job.rb
315
+ # class PublishJob < ApplicationJob
316
+ # def perform(page)
317
+ # RestClient.post(ENV['BUILD_HOOK_URL'])
318
+ # end
319
+ # end
320
+ #
321
+ # # config/initializers/alchemy.rb
322
+ # Alchemy.config.publish_targets << PublishJob
323
+ #
324
+ option :publish_targets, :collection, collection_class: Set, item_type: :class, default: []
325
+
326
+ # Configure tabs in the link dialog
327
+ #
328
+ # With this configuration that tabs in the link dialog can be extended
329
+ # without overwriting or defacing the Admin Interface.
330
+ #
331
+ # == Example
332
+ #
333
+ # # components/acme/link_tab.rb
334
+ # module Acme
335
+ # class LinkTab < ::Alchemy::Admin::LinkDialog::BaseTab
336
+ # def title
337
+ # "Awesome Tab Title"
338
+ # end
339
+ #
340
+ # def name
341
+ # :unique_name
342
+ # end
343
+ #
344
+ # def fields
345
+ # [ title_input, target_select ]
346
+ # end
347
+ # end
348
+ # end
349
+ #
350
+ # # config/initializers/alchemy.rb
351
+ # Alchemy.config.link_dialog_tabs << "Acme::LinkTab"
352
+ #
353
+ option :link_dialog_tabs, :collection, collection_class: Set, item_type: :class, default: [
354
+ "Alchemy::Admin::LinkDialog::InternalTab",
355
+ "Alchemy::Admin::LinkDialog::AnchorTab",
356
+ "Alchemy::Admin::LinkDialog::ExternalTab",
357
+ "Alchemy::Admin::LinkDialog::FileTab"
358
+ ]
221
359
  end
222
360
  end
223
361
  end
@@ -4,8 +4,8 @@ module Alchemy
4
4
  module Configurations
5
5
  class Uploader < Alchemy::Configuration
6
6
  class AllowedFileTypes < Alchemy::Configuration
7
- option :alchemy_attachments, :string_list, default: ["*"]
8
- option :alchemy_pictures, :string_list, default: %w[jpg jpeg gif png svg webp]
7
+ option :alchemy_attachments, :collection, item_type: :string, default: ["*"]
8
+ option :alchemy_pictures, :collection, item_type: :string, default: %w[jpg jpeg gif png svg webp]
9
9
 
10
10
  def set(configuration_hash)
11
11
  super(configuration_hash.transform_keys { transform_key(_1) })
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- Deprecation = ActiveSupport::Deprecation.new("8.0", "Alchemy")
4
+ Deprecation = ActiveSupport::Deprecation.new("9.0", "Alchemy")
5
5
  end
@@ -26,7 +26,7 @@ module Alchemy
26
26
 
27
27
  initializer "alchemy.admin_stylesheets" do |app|
28
28
  if defined?(Sprockets)
29
- Alchemy.admin_stylesheets.each do |stylesheet|
29
+ Alchemy.config.admin_stylesheets.each do |stylesheet|
30
30
  app.config.assets.precompile << stylesheet
31
31
  end
32
32
  end
@@ -42,22 +42,35 @@ module Alchemy
42
42
  end
43
43
  end
44
44
 
45
+ initializer "alchemy.admin_importmap" do
46
+ Alchemy.config.admin_importmaps.add(
47
+ importmap_path: root.join("config/importmap.rb"),
48
+ source_paths: [
49
+ root.join("app/javascript"),
50
+ root.join("vendor/javascript")
51
+ ],
52
+ name: "alchemy_admin"
53
+ )
54
+ end
55
+
45
56
  initializer "alchemy.importmap" do |app|
46
- watch_paths = []
47
-
48
- Alchemy.admin_importmaps.each do |admin_import|
49
- Alchemy.importmap.draw admin_import[:importmap_path]
50
- watch_paths += admin_import[:source_paths]
51
- app.config.assets.paths += admin_import[:source_paths]
52
- if admin_import[:name] != "alchemy_admin"
53
- Alchemy.admin_js_imports.add(admin_import[:name])
57
+ app.config.to_prepare do
58
+ watch_paths = []
59
+
60
+ Alchemy.config.admin_importmaps.each do |admin_import|
61
+ Alchemy.importmap.draw admin_import.importmap_path
62
+ watch_paths += admin_import.source_paths.to_a
63
+ app.config.assets.paths += admin_import.source_paths.to_a
64
+ if admin_import[:name] != "alchemy_admin"
65
+ Alchemy.config.admin_js_imports.add(admin_import.name)
66
+ end
54
67
  end
55
- end
56
68
 
57
- if app.config.importmap.sweep_cache
58
- Alchemy.importmap.cache_sweeper(watches: watch_paths)
59
- ActiveSupport.on_load(:action_controller_base) do
60
- before_action { Alchemy.importmap.cache_sweeper.execute_if_updated }
69
+ if app.config.importmap.sweep_cache
70
+ Alchemy.importmap.cache_sweeper(watches: watch_paths)
71
+ ActiveSupport.on_load(:action_controller_base) do
72
+ before_action { Alchemy.importmap.cache_sweeper.execute_if_updated }
73
+ end
61
74
  end
62
75
  end
63
76
  end
@@ -13,11 +13,20 @@ module Alchemy
13
13
  module ConfigStubbing
14
14
  # Stub a key from the Alchemy config
15
15
  #
16
- # @param key [Symbol] The configuration key you want to stub
17
- # @param value [Object] The value you want to return instead of the original one
16
+ # @param hash [Hash] The keys you would like to stub along with their values
18
17
  #
19
- def stub_alchemy_config(key, value)
20
- allow(Alchemy.config).to receive(key).and_return(value)
18
+ def stub_alchemy_config(hash)
19
+ stub_config(Alchemy.config, hash)
20
+ end
21
+
22
+ def stub_config(config, hash)
23
+ hash.each do |key, value|
24
+ if value.is_a?(Hash)
25
+ stub_config(config.send(key), value)
26
+ else
27
+ allow(config).to receive(key).and_return(value)
28
+ end
29
+ end
21
30
  end
22
31
  end
23
32
  end
@@ -3,7 +3,8 @@
3
3
  FactoryBot.define do
4
4
  factory :alchemy_language, class: "Alchemy::Language" do
5
5
  name { "Your Language" }
6
- code { ::I18n.available_locales.first.to_s }
6
+ language_code { "en" }
7
+ locale { ::I18n.default_locale }
7
8
  default { true }
8
9
  frontpage_name { "Intro" }
9
10
  page_layout { Alchemy.config.default_language.page_layout }
@@ -14,20 +15,23 @@ FactoryBot.define do
14
15
 
15
16
  trait :klingon do
16
17
  name { "Klingon" }
17
- code { "kl" }
18
+ language_code { "kl" }
19
+ locale { :kl }
18
20
  frontpage_name { "Tuq" }
19
21
  default { false }
20
22
  end
21
23
 
22
24
  trait :english do
23
25
  name { "English" }
24
- code { "en" }
26
+ language_code { "en" }
27
+ locale { :en }
25
28
  default { false }
26
29
  end
27
30
 
28
31
  trait :german do
29
32
  name { "Deutsch" }
30
- code { "de" }
33
+ language_code { "de" }
34
+ locale { :de }
31
35
  default { false }
32
36
  end
33
37
  end
@@ -32,6 +32,7 @@ FactoryBot.define do
32
32
  public_on { Time.current }
33
33
  public_until { nil }
34
34
  end
35
+ published_at { Time.current }
35
36
  after(:build) do |page, evaluator|
36
37
  page.build_public_version(
37
38
  public_on: evaluator.public_on,
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "8.0.0.b"
4
+ VERSION = "8.0.0.c"
5
5
 
6
6
  def self.version
7
7
  VERSION