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.
- checksums.yaml +4 -4
- data/README.md +7 -128
- data/app/controllers/view_component/storybook/stories_controller.rb +10 -11
- data/app/views/view_component/storybook/stories/show.html.erb +8 -1
- data/config/locales/en.yml +32 -0
- data/lib/view_component/storybook/content_concern.rb +42 -0
- data/lib/view_component/storybook/controls/base_options_config.rb +41 -0
- data/lib/view_component/storybook/controls/boolean_config.rb +7 -6
- data/lib/view_component/storybook/controls/color_config.rb +4 -5
- data/lib/view_component/storybook/controls/control_config.rb +24 -36
- data/lib/view_component/storybook/controls/controls_helpers.rb +76 -0
- data/lib/view_component/storybook/controls/custom_config.rb +52 -0
- data/lib/view_component/storybook/controls/date_config.rb +14 -11
- data/lib/view_component/storybook/controls/multi_options_config.rb +46 -0
- data/lib/view_component/storybook/controls/number_config.rb +9 -9
- data/lib/view_component/storybook/controls/object_config.rb +13 -5
- data/lib/view_component/storybook/controls/options_config.rb +17 -30
- data/lib/view_component/storybook/controls/simple_control_config.rb +48 -0
- data/lib/view_component/storybook/controls/text_config.rb +1 -1
- data/lib/view_component/storybook/controls.rb +5 -1
- data/lib/view_component/storybook/dsl/{controls_dsl.rb → legacy_controls_dsl.rb} +18 -21
- data/lib/view_component/storybook/dsl.rb +1 -2
- data/lib/view_component/storybook/engine.rb +13 -2
- data/lib/view_component/storybook/method_args/component_constructor_args.rb +23 -0
- data/lib/view_component/storybook/method_args/control_method_args.rb +91 -0
- data/lib/view_component/storybook/method_args/dry_initializer_component_constructor_args.rb +45 -0
- data/lib/view_component/storybook/method_args/method_args.rb +52 -0
- data/lib/view_component/storybook/method_args/method_parameters_names.rb +97 -0
- data/lib/view_component/storybook/method_args.rb +17 -0
- data/lib/view_component/storybook/slots/slot.rb +24 -0
- data/lib/view_component/storybook/slots/slot_config.rb +79 -0
- data/lib/view_component/storybook/slots.rb +14 -0
- data/lib/view_component/storybook/stories.rb +60 -10
- data/lib/view_component/storybook/story.rb +18 -0
- data/lib/view_component/storybook/story_config.rb +143 -15
- data/lib/view_component/storybook/tasks/write_stories_json.rake +6 -0
- data/lib/view_component/storybook/version.rb +1 -1
- data/lib/view_component/storybook.rb +25 -0
- metadata +64 -20
- data/lib/view_component/storybook/controls/array_config.rb +0 -36
- 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 <
|
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,
|
15
|
-
super(
|
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
|
23
|
-
|
24
|
-
|
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
|
-
|
27
|
+
params_value
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
31
|
private
|
31
32
|
|
32
33
|
def csf_control_params
|
33
|
-
|
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 <
|
6
|
+
class ObjectConfig < SimpleControlConfig
|
7
7
|
def type
|
8
8
|
:object
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
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
|
-
|
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 <
|
7
|
-
|
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 :
|
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
|
35
|
-
|
36
|
-
|
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
|
-
|
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(
|
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
|
@@ -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 :
|
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
|
7
|
-
|
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(
|
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(
|
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,
|
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,
|
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(
|
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(
|
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,
|
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',
|
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,
|
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',
|
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,
|
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',
|
56
|
+
controls << Controls::OptionsConfig.new(:'inline-check', options, value, param: param, name: name)
|
60
57
|
end
|
61
58
|
|
62
|
-
def array(param, value,
|
63
|
-
controls << Controls::
|
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(
|
64
|
+
controls << Controls::DateConfig.new(value, param: param, name: name)
|
68
65
|
end
|
69
66
|
|
70
67
|
def respond_to_missing?(_method, *)
|
@@ -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 "
|
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
|