view_component_storybook 0.8.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|