view_component_storybook 0.8.0 → 0.11.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -128
  3. data/app/controllers/view_component/storybook/stories_controller.rb +10 -11
  4. data/app/views/view_component/storybook/stories/show.html.erb +8 -1
  5. data/config/locales/en.yml +32 -0
  6. data/lib/view_component/storybook/content_concern.rb +42 -0
  7. data/lib/view_component/storybook/controls/base_options_config.rb +41 -0
  8. data/lib/view_component/storybook/controls/boolean_config.rb +7 -6
  9. data/lib/view_component/storybook/controls/color_config.rb +4 -5
  10. data/lib/view_component/storybook/controls/control_config.rb +24 -36
  11. data/lib/view_component/storybook/controls/controls_helpers.rb +76 -0
  12. data/lib/view_component/storybook/controls/custom_config.rb +52 -0
  13. data/lib/view_component/storybook/controls/date_config.rb +14 -11
  14. data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
  15. data/lib/view_component/storybook/controls/number_config.rb +9 -9
  16. data/lib/view_component/storybook/controls/object_config.rb +13 -5
  17. data/lib/view_component/storybook/controls/options_config.rb +17 -30
  18. data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
  19. data/lib/view_component/storybook/controls/text_config.rb +1 -1
  20. data/lib/view_component/storybook/controls.rb +5 -1
  21. data/lib/view_component/storybook/dsl/{controls_dsl.rb → legacy_controls_dsl.rb} +18 -21
  22. data/lib/view_component/storybook/dsl.rb +1 -2
  23. data/lib/view_component/storybook/engine.rb +13 -2
  24. data/lib/view_component/storybook/method_args/component_constructor_args.rb +23 -0
  25. data/lib/view_component/storybook/method_args/control_method_args.rb +91 -0
  26. data/lib/view_component/storybook/method_args/dry_initializer_component_constructor_args.rb +45 -0
  27. data/lib/view_component/storybook/method_args/method_args.rb +52 -0
  28. data/lib/view_component/storybook/method_args/method_parameters_names.rb +97 -0
  29. data/lib/view_component/storybook/method_args.rb +17 -0
  30. data/lib/view_component/storybook/slots/slot.rb +24 -0
  31. data/lib/view_component/storybook/slots/slot_config.rb +79 -0
  32. data/lib/view_component/storybook/slots.rb +14 -0
  33. data/lib/view_component/storybook/stories.rb +60 -10
  34. data/lib/view_component/storybook/story.rb +18 -0
  35. data/lib/view_component/storybook/story_config.rb +143 -15
  36. data/lib/view_component/storybook/tasks/write_stories_json.rake +6 -0
  37. data/lib/view_component/storybook/version.rb +1 -1
  38. data/lib/view_component/storybook.rb +25 -0
  39. metadata +64 -20
  40. data/lib/view_component/storybook/controls/array_config.rb +0 -36
  41. data/lib/view_component/storybook/dsl/story_dsl.rb +0 -39
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ class MultiOptionsConfig < BaseOptionsConfig
7
+ TYPES = %i[multi-select check inline-check].freeze
8
+
9
+ validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
10
+ validate :validate_default_value, unless: -> { options.nil? || default_value.nil? }
11
+
12
+ def initialize(type, options, default_value, labels: nil, param: nil, name: nil)
13
+ super(type, options, Array.wrap(default_value), labels: labels, param: param, name: name)
14
+ end
15
+
16
+ def value_from_params(params)
17
+ params_value = super(params)
18
+
19
+ if params_value.is_a?(String)
20
+ params_value = params_value.split(',')
21
+ params_value = params_value.map(&:to_sym) if symbol_values
22
+ end
23
+ params_value
24
+ end
25
+
26
+ def to_csf_params
27
+ super.deep_merge(argTypes: { param => { options: options } })
28
+ end
29
+
30
+ private
31
+
32
+ def csf_control_params
33
+ labels.nil? ? super : super.merge(labels: labels)
34
+ end
35
+
36
+ def symbol_values
37
+ @symbol_values ||= default_value.first.is_a?(Symbol)
38
+ end
39
+
40
+ def validate_default_value
41
+ errors.add(:default_value, :inclusion) unless default_value.to_set <= options.to_set
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class NumberConfig < ControlConfig
6
+ class NumberConfig < SimpleControlConfig
7
7
  TYPES = %i[number range].freeze
8
8
 
9
9
  attr_reader :type, :min, :max, :step
@@ -11,27 +11,27 @@ module ViewComponent
11
11
  validates :type, presence: true
12
12
  validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
13
13
 
14
- def initialize(type, component, param, value, min: nil, max: nil, step: nil, name: nil)
15
- super(component, param, value, name: name)
14
+ def initialize(type, default_value, min: nil, max: nil, step: nil, param: nil, name: nil)
15
+ super(default_value, param: param, name: name)
16
16
  @type = type
17
17
  @min = min
18
18
  @max = max
19
19
  @step = step
20
20
  end
21
21
 
22
- def value_from_param(param)
23
- if param.is_a?(String) && param.present?
24
- (param.to_f % 1) > 0 ? param.to_f : param.to_i
22
+ def value_from_params(params)
23
+ params_value = super(params)
24
+ if params_value.is_a?(String) && params_value.present?
25
+ (params_value.to_f % 1) > 0 ? params_value.to_f : params_value.to_i
25
26
  else
26
- super(param)
27
+ params_value
27
28
  end
28
29
  end
29
30
 
30
31
  private
31
32
 
32
33
  def csf_control_params
33
- params = super
34
- params.merge(min: min, max: max, step: step).compact
34
+ super.merge(min: min, max: max, step: step).compact
35
35
  end
36
36
  end
37
37
  end
@@ -3,16 +3,24 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class ObjectConfig < ControlConfig
6
+ class ObjectConfig < SimpleControlConfig
7
7
  def type
8
8
  :object
9
9
  end
10
10
 
11
- def value_from_param(param)
12
- if param.is_a?(String)
13
- JSON.parse(param).deep_symbolize_keys
11
+ def value_from_params(params)
12
+ params_value = super(params)
13
+ if params_value.is_a?(String)
14
+ parsed_json = JSON.parse(params_value)
15
+ if parsed_json.is_a?(Array)
16
+ parsed_json.map do |item|
17
+ item.is_a?(Hash) ? item.deep_symbolize_keys : item
18
+ end
19
+ else
20
+ parsed_json.deep_symbolize_keys
21
+ end
14
22
  else
15
- super(param)
23
+ params_value
16
24
  end
17
25
  end
18
26
  end
@@ -3,46 +3,33 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class OptionsConfig < ControlConfig
7
- class << self
8
- # support the options being a Hash or an Array. Storybook supports either.
9
- def inclusion_in(config)
10
- case config.options
11
- when Hash
12
- config.options.values
13
- when Array
14
- config.options
15
- end
16
- end
17
- end
18
-
19
- TYPES = %i[select multi-select radio inline-radio check inline-check].freeze
6
+ class OptionsConfig < BaseOptionsConfig
7
+ TYPES = %i[select radio inline-radio].freeze
20
8
 
21
- attr_reader :type, :options, :symbol_value
22
-
23
- validates :type, :options, presence: true
24
9
  validates :type, inclusion: { in: TYPES }, unless: -> { type.nil? }
25
- validates :value, inclusion: { in: method(:inclusion_in) }, unless: -> { options.nil? || value.nil? }
26
-
27
- def initialize(type, component, param, options, default_value, name: nil)
28
- super(component, param, default_value, name: name)
29
- @type = type
30
- @options = options
31
- @symbol_value = default_value.is_a?(Symbol)
32
- end
10
+ validates :default_value, inclusion: { in: ->(config) { config.options } }, unless: -> { options.nil? || default_value.nil? }
33
11
 
34
- def value_from_param(param)
35
- if param.is_a?(String) && symbol_value
36
- param.to_sym
12
+ def value_from_params(params)
13
+ params_value = super(params)
14
+ if params_value.is_a?(String) && symbol_value
15
+ params_value.to_sym
37
16
  else
38
- super(param)
17
+ params_value
39
18
  end
40
19
  end
41
20
 
21
+ def to_csf_params
22
+ super.deep_merge(argTypes: { param => { options: options } })
23
+ end
24
+
42
25
  private
43
26
 
44
27
  def csf_control_params
45
- super.merge(options: options)
28
+ labels.nil? ? super : super.merge(labels: labels)
29
+ end
30
+
31
+ def symbol_value
32
+ @symbol_value ||= default_value.is_a?(Symbol)
46
33
  end
47
34
  end
48
35
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module Controls
6
+ ##
7
+ # A simple Control Config maps to one Storybook Control
8
+ # It has a value and pulls its value from params by key
9
+ class SimpleControlConfig < ControlConfig
10
+ attr_reader :default_value
11
+
12
+ def initialize(default_value, param: nil, name: nil)
13
+ super(param: param, name: name)
14
+ @default_value = default_value
15
+ end
16
+
17
+ def to_csf_params
18
+ validate!
19
+ {
20
+ args: { param => csf_value },
21
+ argTypes: { param => { control: csf_control_params, name: name } }
22
+ }
23
+ end
24
+
25
+ def value_from_params(params)
26
+ params.key?(param) ? params[param] : default_value
27
+ end
28
+
29
+ private
30
+
31
+ # provide extension points for subclasses to vary the value
32
+ def type
33
+ # :nocov:
34
+ raise NotImplementedError
35
+ # :nocov:
36
+ end
37
+
38
+ def csf_value
39
+ default_value
40
+ end
41
+
42
+ def csf_control_params
43
+ { type: type }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Controls
6
- class TextConfig < ControlConfig
6
+ class TextConfig < SimpleControlConfig
7
7
  def type
8
8
  :text
9
9
  end
@@ -8,14 +8,18 @@ module ViewComponent
8
8
  extend ActiveSupport::Autoload
9
9
 
10
10
  autoload :ControlConfig
11
+ autoload :SimpleControlConfig
11
12
  autoload :TextConfig
12
13
  autoload :BooleanConfig
13
14
  autoload :ColorConfig
14
15
  autoload :NumberConfig
16
+ autoload :BaseOptionsConfig
15
17
  autoload :OptionsConfig
16
- autoload :ArrayConfig
18
+ autoload :MultiOptionsConfig
17
19
  autoload :DateConfig
18
20
  autoload :ObjectConfig
21
+ autoload :CustomConfig
22
+ autoload :ControlsHelpers
19
23
  end
20
24
  end
21
25
  end
@@ -3,68 +3,65 @@
3
3
  module ViewComponent
4
4
  module Storybook
5
5
  module Dsl
6
- class ControlsDsl
7
- attr_reader :component, :controls
8
-
9
- def initialize(component)
10
- @component = component
11
- @controls = []
6
+ class LegacyControlsDsl
7
+ def controls
8
+ @controls ||= []
12
9
  end
13
10
 
14
11
  def text(param, value, name: nil)
15
- controls << Controls::TextConfig.new(component, param, value, name: name)
12
+ controls << Controls::TextConfig.new(value, param: param, name: name)
16
13
  end
17
14
 
18
15
  def boolean(param, value, name: nil)
19
- controls << Controls::BooleanConfig.new(component, param, value, name: name)
16
+ controls << Controls::BooleanConfig.new(value, param: param, name: name)
20
17
  end
21
18
 
22
19
  def number(param, value, name: nil, min: nil, max: nil, step: nil)
23
- controls << Controls::NumberConfig.new(:number, component, param, value, name: name, min: min, max: max, step: step)
20
+ controls << Controls::NumberConfig.new(:number, value, param: param, name: name, min: min, max: max, step: step)
24
21
  end
25
22
 
26
23
  def range(param, value, name: nil, min: nil, max: nil, step: nil)
27
- controls << Controls::NumberConfig.new(:range, component, param, value, name: name, min: min, max: max, step: step)
24
+ controls << Controls::NumberConfig.new(:range, value, param: param, name: name, min: min, max: max, step: step)
28
25
  end
29
26
 
30
27
  def color(param, value, name: nil, preset_colors: nil)
31
- controls << Controls::ColorConfig.new(component, param, value, name: name, preset_colors: preset_colors)
28
+ controls << Controls::ColorConfig.new(value, param: param, name: name, preset_colors: preset_colors)
32
29
  end
33
30
 
34
31
  def object(param, value, name: nil)
35
- controls << Controls::ObjectConfig.new(component, param, value, name: name)
32
+ controls << Controls::ObjectConfig.new(value, param: param, name: name)
36
33
  end
37
34
 
38
35
  def select(param, options, value, name: nil)
39
- controls << Controls::OptionsConfig.new(:select, component, param, options, value, name: name)
36
+ controls << Controls::OptionsConfig.new(:select, options, value, param: param, name: name)
40
37
  end
41
38
 
42
39
  def multi_select(param, options, value, name: nil)
43
- controls << Controls::OptionsConfig.new(:'multi-select', component, param, options, value, name: name)
40
+ controls << Controls::OptionsConfig.new(:'multi-select', options, value, param: param, name: name)
44
41
  end
45
42
 
46
43
  def radio(param, options, value, name: nil)
47
- controls << Controls::OptionsConfig.new(:radio, component, param, options, value, name: name)
44
+ controls << Controls::OptionsConfig.new(:radio, options, value, param: param, name: name)
48
45
  end
49
46
 
50
47
  def inline_radio(param, options, value, name: nil)
51
- controls << Controls::OptionsConfig.new(:'inline-radio', component, param, options, value, name: name)
48
+ controls << Controls::OptionsConfig.new(:'inline-radio', options, value, param: param, name: name)
52
49
  end
53
50
 
54
51
  def check(param, options, value, name: nil)
55
- controls << Controls::OptionsConfig.new(:check, component, param, options, value, name: name)
52
+ controls << Controls::OptionsConfig.new(:check, options, value, param: param, name: name)
56
53
  end
57
54
 
58
55
  def inline_check(param, options, value, name: nil)
59
- controls << Controls::OptionsConfig.new(:'inline-check', component, param, options, value, name: name)
56
+ controls << Controls::OptionsConfig.new(:'inline-check', options, value, param: param, name: name)
60
57
  end
61
58
 
62
- def array(param, value, separator = ",", name: nil)
63
- controls << Controls::ArrayConfig.new(component, param, value, separator, name: name)
59
+ def array(param, value, _separator = nil, name: nil)
60
+ controls << Controls::ObjectConfig.new(value, param: param, name: name)
64
61
  end
65
62
 
66
63
  def date(param, value, name: nil)
67
- controls << Controls::DateConfig.new(component, param, value, name: name)
64
+ controls << Controls::DateConfig.new(value, param: param, name: name)
68
65
  end
69
66
 
70
67
  def respond_to_missing?(_method, *)
@@ -7,8 +7,7 @@ module ViewComponent
7
7
  module Dsl
8
8
  extend ActiveSupport::Autoload
9
9
 
10
- autoload :StoryDsl
11
- autoload :ControlsDsl
10
+ autoload :LegacyControlsDsl
12
11
  end
13
12
  end
14
13
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails"
4
- require "view_component/storybook"
5
4
 
6
5
  module ViewComponent
7
6
  module Storybook
@@ -12,6 +11,7 @@ module ViewComponent
12
11
  options = app.config.view_component_storybook
13
12
 
14
13
  options.show_stories = Rails.env.development? if options.show_stories.nil?
14
+ options.stories_route ||= ViewComponent::Storybook.stories_route
15
15
 
16
16
  if options.show_stories
17
17
  options.stories_path ||= defined?(Rails.root) ? Rails.root.join("test/components/stories") : nil
@@ -33,7 +33,7 @@ module ViewComponent
33
33
 
34
34
  if options.show_stories
35
35
  app.routes.prepend do
36
- get "/rails/stories/*stories/:story" => "view_component/storybook/stories#show", :internal => true
36
+ get "#{options.stories_route}/*stories/:story" => "view_component/storybook/stories#show", :internal => true
37
37
  end
38
38
  end
39
39
  end
@@ -44,3 +44,14 @@ module ViewComponent
44
44
  end
45
45
  end
46
46
  end
47
+
48
+ # :nocov:
49
+ unless defined?(ViewComponent::Storybook)
50
+ ActiveSupport::Deprecation.warn(
51
+ "This manually engine loading is deprecated and will be removed in v1.0.0. " \
52
+ "Remove `require \"view_component/storybook/engine\"`."
53
+ )
54
+
55
+ require "view_component/storybook"
56
+ end
57
+ # :nocov:
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module MethodArgs
6
+ ##
7
+ # Class representing the constructor args for a Component
8
+ class ComponentConstructorArgs < ControlMethodArgs
9
+ def self.from_component_class(component_class, *args, **kwargs)
10
+ if DryInitializerComponentConstructorArgs.dry_initialize?(component_class)
11
+ DryInitializerComponentConstructorArgs.new(component_class, *args, **kwargs)
12
+ else
13
+ new(component_class, *args, **kwargs)
14
+ end
15
+ end
16
+
17
+ def initialize(component_class, *args, **kwargs)
18
+ super(component_class.instance_method(:initialize), *args, **kwargs)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module MethodArgs
6
+ ##
7
+ # Class representing arguments passed to a method which can be validated
8
+ # against the args of the target method
9
+ # In addition the args and kwargs can contain Controls the values of which can
10
+ # be resolved from a params hash
11
+ class ControlMethodArgs < MethodArgs
12
+ include ActiveModel::Validations::Callbacks
13
+
14
+ attr_reader :param_prefix
15
+
16
+ validate :validate_controls
17
+ before_validation :assign_control_params
18
+
19
+ def with_param_prefix(prefix)
20
+ @param_prefix = prefix
21
+ self
22
+ end
23
+
24
+ ##
25
+ # resolve the controls values from the params
26
+ # call the target method or block with those values
27
+ def call(params, &target_block)
28
+ method_args = resolve_method_args(params)
29
+
30
+ (target_block || target_method).call(*method_args.args, **method_args.kwargs)
31
+ end
32
+
33
+ def resolve_method_args(params)
34
+ assign_control_params
35
+
36
+ args_from_params = args.map do |arg|
37
+ value_from_params(arg, params)
38
+ end
39
+ kwargs_from_params = kwargs.transform_values do |arg|
40
+ value_from_params(arg, params)
41
+ end
42
+
43
+ MethodArgs.new(target_method, *args_from_params, **kwargs_from_params)
44
+ end
45
+
46
+ def controls
47
+ @controls ||= (args + kwargs.values).select(&method(:control?))
48
+ end
49
+
50
+ private
51
+
52
+ def assign_control_params
53
+ return if @assigned_control_params
54
+
55
+ args.each_with_index do |arg, index|
56
+ add_param_if_control(arg, target_method_params_names.arg_name(index))
57
+ end
58
+
59
+ kwargs.each do |key, arg|
60
+ add_param_if_control(arg, key)
61
+ end
62
+
63
+ @assigned_control_params = true
64
+ end
65
+
66
+ def add_param_if_control(arg, param)
67
+ return unless control?(arg)
68
+
69
+ arg.param(param) if arg.param.nil? # don't overrite if set
70
+ # Always add prefix
71
+ arg.prefix_param(param_prefix) if param_prefix.present?
72
+ end
73
+
74
+ def value_from_params(arg, params)
75
+ control?(arg) ? arg.value_from_params(params) : arg
76
+ end
77
+
78
+ def control?(arg)
79
+ arg.is_a?(Controls::ControlConfig)
80
+ end
81
+
82
+ def validate_controls
83
+ controls.reject(&:valid?).each do |control|
84
+ control_errors = control.errors.full_messages.join(', ')
85
+ errors.add(:controls, :invalid_control, control_name: control.name, control_errors: control_errors)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module MethodArgs
6
+ ##
7
+ # Class representing the constructor args for a Component the extends dry-initializer
8
+ class DryInitializerComponentConstructorArgs < ControlMethodArgs
9
+ INITIALIZE_METHOD = :__dry_initializer_initialize__
10
+
11
+ ##
12
+ # Method Parameters names that are dry-initialize aware
13
+ # THe main difference is that Dry Initializer keyword args cannot be deduced from the constructor initialize params
14
+ # Instead we have to introspect the dry_initializer config for the option definitions
15
+ class DryConstructorParametersNames < MethodParametersNames
16
+ attr_reader :dry_initializer
17
+
18
+ def initialize(component_class)
19
+ super(component_class.instance_method(INITIALIZE_METHOD))
20
+ @dry_initializer = component_class.dry_initializer
21
+ end
22
+
23
+ # Required keywords are the only thing we need.
24
+ # We could define kwarg_names similarly but wihout the `optional` check. However, dry-initializer
25
+ # always ends has supports_keyrest? == true so kwarg_names isn't needed
26
+ def req_kwarg_names
27
+ @req_kwarg_names ||= dry_initializer.definitions.map do |name, definition|
28
+ name if definition.option && !definition.optional
29
+ end.compact
30
+ end
31
+ end
32
+
33
+ def self.dry_initialize?(component_class)
34
+ component_class.private_method_defined?(INITIALIZE_METHOD)
35
+ end
36
+
37
+ def initialize(component_class, *args, **kwargs)
38
+ super(component_class.instance_method(INITIALIZE_METHOD), *args, **kwargs)
39
+
40
+ @target_method_params_names = DryConstructorParametersNames.new(component_class)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module Storybook
5
+ module MethodArgs
6
+ ##
7
+ # Class representing arguments passed to a method which can be validated
8
+ # against the args of the target method
9
+ class MethodArgs
10
+ include ActiveModel::Validations
11
+
12
+ attr_reader :args, :kwargs, :target_method, :target_method_params_names
13
+
14
+ validate :validate_args, :validate_kwargs
15
+
16
+ def initialize(target_method, *args, **kwargs)
17
+ @target_method = target_method
18
+ @args = args
19
+ @kwargs = kwargs
20
+ @target_method_params_names = MethodParametersNames.new(target_method)
21
+ end
22
+
23
+ private
24
+
25
+ def validate_args
26
+ arg_count = args.count
27
+
28
+ if arg_count > target_method_params_names.max_arg_count
29
+ errors.add(:args, :too_many, max: target_method_params_names.max_arg_count, count: arg_count)
30
+ elsif arg_count < target_method_params_names.min_arg_count
31
+ errors.add(:args, :too_few, min: target_method_params_names.min_arg_count, count: arg_count)
32
+ end
33
+ end
34
+
35
+ def validate_kwargs
36
+ kwargs.each_key do |kwarg|
37
+ unless target_method_params_names.include_kwarg?(kwarg)
38
+ errors.add(:kwargs, :invalid_arg, kwarg: kwarg)
39
+ end
40
+ end
41
+
42
+ return if target_method_params_names.covers_required_kwargs?(kwargs.keys)
43
+
44
+ expected_keys = target_method_params_names.req_kwarg_names.join(', ')
45
+ actual_keys = kwargs.keys.join(', ')
46
+
47
+ errors.add(:kwargs, :invalid, expected_keys: expected_keys, actual_keys: actual_keys)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end