vident 0.7.0 → 0.8.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 +51 -28
- data/Rakefile +1 -12
- data/lib/tasks/vident_tasks.rake +4 -0
- data/lib/vident/attributes/not_typed.rb +3 -0
- data/lib/vident/base.rb +8 -29
- data/lib/vident/component.rb +0 -2
- data/lib/vident/engine.rb +15 -0
- data/lib/vident/root_component.rb +235 -0
- data/lib/vident/version.rb +1 -1
- data/lib/vident.rb +3 -36
- metadata +11 -30
- data/.standard.yml +0 -3
- data/CHANGELOG.md +0 -79
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -35
- data/LICENSE.txt +0 -21
- data/examples/avatar.png +0 -0
- data/examples/ex1.gif +0 -0
- data/lib/tasks/vident.rake +0 -37
- data/lib/vident/attributes/typed.rb +0 -229
- data/lib/vident/attributes/typed_niling_struct.rb +0 -27
- data/lib/vident/attributes/types.rb +0 -16
- data/lib/vident/caching/cache_key.rb +0 -144
- data/lib/vident/railtie.rb +0 -10
- data/lib/vident/root_component/base.rb +0 -237
- data/lib/vident/root_component/using_better_html.rb +0 -41
- data/lib/vident/root_component/using_phlex_html.rb +0 -50
- data/lib/vident/root_component/using_view_component.rb +0 -51
- data/lib/vident/tailwind.rb +0 -12
- data/lib/vident/test_case.rb +0 -8
- data/lib/vident/testing/attributes_tester.rb +0 -176
- data/lib/vident/testing/auto_test.rb +0 -70
- data/lib/vident/typed_component.rb +0 -48
- data/sig/vident.rbs +0 -4
data/lib/vident/railtie.rb
DELETED
@@ -1,237 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Vident
|
4
|
-
module RootComponent
|
5
|
-
module Base
|
6
|
-
def initialize(
|
7
|
-
controllers: nil,
|
8
|
-
actions: nil,
|
9
|
-
targets: nil,
|
10
|
-
named_classes: nil, # https://stimulus.hotwired.dev/reference/css-classes
|
11
|
-
data_maps: nil,
|
12
|
-
element_tag: nil,
|
13
|
-
id: nil,
|
14
|
-
html_options: nil
|
15
|
-
)
|
16
|
-
@element_tag = element_tag
|
17
|
-
@html_options = html_options
|
18
|
-
@id = id
|
19
|
-
@controllers = Array.wrap(controllers)
|
20
|
-
@actions = actions
|
21
|
-
@targets = targets
|
22
|
-
@named_classes = named_classes
|
23
|
-
@data_map_kvs = {}
|
24
|
-
@data_maps = data_maps
|
25
|
-
end
|
26
|
-
|
27
|
-
# The view component's helpers for setting stimulus data-* attributes on this component.
|
28
|
-
|
29
|
-
# TODO: rename
|
30
|
-
# Create a Stimulus action string, and returns it
|
31
|
-
# examples:
|
32
|
-
# action(:my_thing) => "current_controller#myThing"
|
33
|
-
# action(:click, :my_thing) => "click->current_controller#myThing"
|
34
|
-
# action("click->current_controller#myThing") => "click->current_controller#myThing"
|
35
|
-
# action("path/to/current", :my_thing) => "path--to--current_controller#myThing"
|
36
|
-
# action(:click, "path/to/current", :my_thing) => "click->path--to--current_controller#myThing"
|
37
|
-
def action(*args)
|
38
|
-
part1, part2, part3 = args
|
39
|
-
(args.size == 1) ? parse_action_arg(part1) : parse_multiple_action_args(part1, part2, part3)
|
40
|
-
end
|
41
|
-
|
42
|
-
def action_data_attribute(*actions)
|
43
|
-
{action: parse_actions(actions).join(" ")}
|
44
|
-
end
|
45
|
-
|
46
|
-
# TODO: rename & make stimulus Target class instance and returns it, which can convert to String
|
47
|
-
# Create a Stimulus Target and returns it
|
48
|
-
# examples:
|
49
|
-
# target(:my_target) => {controller: 'current_controller' name: 'myTarget'}
|
50
|
-
# target("path/to/current", :my_target) => {controller: 'path--to--current_controller', name: 'myTarget'}
|
51
|
-
def target(name, part2 = nil)
|
52
|
-
if part2.nil?
|
53
|
-
{controller: implied_controller_name, name: js_name(name)}
|
54
|
-
else
|
55
|
-
{controller: stimulize_path(name), name: js_name(part2)}
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def target_data_attribute(name)
|
60
|
-
build_target_data_attributes([target(name)])
|
61
|
-
end
|
62
|
-
|
63
|
-
# Getter for a named classes list so can be used in view to set initial state on SSR
|
64
|
-
# Returns a String of classes that can be used in a `class` attribute.
|
65
|
-
def named_classes(*names)
|
66
|
-
names.map { |name| convert_classes_list_to_string(@named_classes[name]) }.join(" ")
|
67
|
-
end
|
68
|
-
|
69
|
-
# Helpers for generating the Stimulus data-* attributes directly
|
70
|
-
|
71
|
-
# Return the HTML `data-controller` attribute for the given controllers
|
72
|
-
def with_controllers(*controllers_to_set)
|
73
|
-
"data-controller=\"#{controller_list(controllers_to_set)}\"".html_safe
|
74
|
-
end
|
75
|
-
|
76
|
-
# Return the HTML `data-target` attribute for the given targets
|
77
|
-
def as_targets(*targets)
|
78
|
-
attrs = build_target_data_attributes(parse_targets(targets))
|
79
|
-
attrs.map { |dt, n| "data-#{dt}=\"#{n}\"" }.join(" ").html_safe
|
80
|
-
end
|
81
|
-
alias_method :as_target, :as_targets
|
82
|
-
|
83
|
-
# Return the HTML `data-action` attribute for the given actions
|
84
|
-
def with_actions(*actions_to_set)
|
85
|
-
"data-action='#{parse_actions(actions_to_set).join(" ")}'".html_safe
|
86
|
-
end
|
87
|
-
alias_method :with_action, :with_actions
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
|
-
# An implicit Stimulus controller name is built from the implicit controller path
|
92
|
-
def implied_controller_name
|
93
|
-
stimulize_path(implied_controller_path)
|
94
|
-
end
|
95
|
-
|
96
|
-
# When using the DSL if you dont specify, the first controller is implied
|
97
|
-
def implied_controller_path
|
98
|
-
@controllers&.first || raise(StandardError, "No controllers have been specified")
|
99
|
-
end
|
100
|
-
|
101
|
-
# A complete list of Stimulus controllers for this component
|
102
|
-
def controller_list(controllers_to_set)
|
103
|
-
controllers_to_set&.map { |c| stimulize_path(c) }&.join(" ")
|
104
|
-
end
|
105
|
-
|
106
|
-
# Complete list of actions ready to be use in the data-action attribute
|
107
|
-
def action_list(actions_to_parse)
|
108
|
-
return nil unless actions_to_parse&.size&.positive?
|
109
|
-
parse_actions(actions_to_parse).join(" ")
|
110
|
-
end
|
111
|
-
|
112
|
-
# Complete list of targets ready to be use in the data attributes
|
113
|
-
def target_list
|
114
|
-
return {} unless @targets&.size&.positive?
|
115
|
-
build_target_data_attributes(parse_targets(@targets))
|
116
|
-
end
|
117
|
-
|
118
|
-
def named_classes_list
|
119
|
-
return {} unless @named_classes&.size&.positive?
|
120
|
-
build_named_classes_data_attributes(@named_classes)
|
121
|
-
end
|
122
|
-
|
123
|
-
# stimulus "data-*" attributes map for this component
|
124
|
-
def tag_data_attributes
|
125
|
-
{controller: controller_list(@controllers), action: action_list(@actions)}
|
126
|
-
.merge!(target_list)
|
127
|
-
.merge!(named_classes_list)
|
128
|
-
.merge!(data_map_attributes)
|
129
|
-
.compact_blank!
|
130
|
-
end
|
131
|
-
|
132
|
-
# Actions can be specified as a symbol, in which case they imply an action on the primary
|
133
|
-
# controller, or as a string in which case it implies an action that is already fully qualified
|
134
|
-
# stimulus action.
|
135
|
-
# 1 Symbol: :my_action => "my_controller#myAction"
|
136
|
-
# 1 String: "my_controller#myAction"
|
137
|
-
# 2 Symbols: [:click, :my_action] => "click->my_controller#myAction"
|
138
|
-
# 1 String, 1 Symbol: ["path/to/controller", :my_action] => "path--to--controller#myAction"
|
139
|
-
# 1 Symbol, 1 String, 1 Symbol: [:hover, "path/to/controller", :my_action] => "hover->path--to--controller#myAction"
|
140
|
-
|
141
|
-
def parse_action_arg(part1)
|
142
|
-
if part1.is_a?(Symbol)
|
143
|
-
# 1 symbol arg, name of method on this controller
|
144
|
-
"#{implied_controller_name}##{js_name(part1)}"
|
145
|
-
elsif part1.is_a?(String)
|
146
|
-
# 1 string arg, fully qualified action
|
147
|
-
part1
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def parse_multiple_action_args(part1, part2, part3)
|
152
|
-
if part3.nil? && part1.is_a?(Symbol)
|
153
|
-
# 2 symbol args = event + action
|
154
|
-
"#{part1}->#{implied_controller_name}##{js_name(part2)}"
|
155
|
-
elsif part3.nil?
|
156
|
-
# 1 string arg, 1 symbol = controller + action
|
157
|
-
"#{stimulize_path(part1)}##{js_name(part2)}"
|
158
|
-
else
|
159
|
-
# 1 symbol, 1 string, 1 symbol = as above but with event
|
160
|
-
"#{part1}->#{stimulize_path(part2)}##{js_name(part3)}"
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
# Parse actions, targets and attributes that are passed in as symbols or strings
|
165
|
-
|
166
|
-
def parse_targets(targets)
|
167
|
-
targets.map { |n| parse_target(n) }
|
168
|
-
end
|
169
|
-
|
170
|
-
def parse_target(raw_target)
|
171
|
-
return raw_target if raw_target.is_a?(String)
|
172
|
-
return raw_target if raw_target.is_a?(Hash)
|
173
|
-
target(raw_target)
|
174
|
-
end
|
175
|
-
|
176
|
-
def build_target_data_attributes(targets)
|
177
|
-
targets.map { |t| ["#{t[:controller]}-target".to_sym, t[:name]] }.to_h
|
178
|
-
end
|
179
|
-
|
180
|
-
def parse_actions(actions)
|
181
|
-
actions.map! { |a| a.is_a?(String) ? a : action(*a) }
|
182
|
-
end
|
183
|
-
|
184
|
-
def parse_attributes(attrs, controller = nil)
|
185
|
-
attrs.transform_keys { |k| "#{controller || implied_controller_name}-#{k}" }
|
186
|
-
end
|
187
|
-
|
188
|
-
def data_map_attributes
|
189
|
-
return {} unless @data_maps
|
190
|
-
@data_maps.each_with_object({}) do |m, obj|
|
191
|
-
if m.is_a?(Hash)
|
192
|
-
obj.merge!(parse_attributes(m))
|
193
|
-
elsif m.is_a?(Array)
|
194
|
-
controller_path = m.first
|
195
|
-
data = m.last
|
196
|
-
obj.merge!(parse_attributes(data, stimulize_path(controller_path)))
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def parse_named_classes_hash(named_classes)
|
202
|
-
named_classes.map do |name, classes|
|
203
|
-
logical_name = name.to_s.dasherize
|
204
|
-
classes_str = convert_classes_list_to_string(classes)
|
205
|
-
if classes.is_a?(Hash)
|
206
|
-
{controller: stimulize_path(classes[:controller_path]), name: logical_name, classes: classes_str}
|
207
|
-
else
|
208
|
-
{controller: implied_controller_name, name: logical_name, classes: classes_str}
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def build_named_classes_data_attributes(named_classes)
|
214
|
-
parse_named_classes_hash(named_classes)
|
215
|
-
.map { |c| ["#{c[:controller]}-#{c[:name]}-class", c[:classes]] }
|
216
|
-
.to_h
|
217
|
-
end
|
218
|
-
|
219
|
-
def convert_classes_list_to_string(classes)
|
220
|
-
return "" if classes.nil?
|
221
|
-
return classes if classes.is_a?(String)
|
222
|
-
return classes.join(" ") if classes.is_a?(Array)
|
223
|
-
classes[:classes].is_a?(Array) ? classes[:classes].join(" ") : classes[:classes]
|
224
|
-
end
|
225
|
-
|
226
|
-
# Convert a file path to a stimulus controller name
|
227
|
-
def stimulize_path(path)
|
228
|
-
path.split("/").map { |p| p.to_s.dasherize }.join("--")
|
229
|
-
end
|
230
|
-
|
231
|
-
# Convert a Ruby 'snake case' string to a JavaScript camel case strings
|
232
|
-
def js_name(name)
|
233
|
-
name.to_s.camelize(:lower)
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
if Gem.loaded_specs.has_key? "better_html"
|
4
|
-
require "better_html"
|
5
|
-
require "cgi/util"
|
6
|
-
|
7
|
-
module Vident
|
8
|
-
module RootComponent
|
9
|
-
module UsingBetterHTML
|
10
|
-
# Return the HTML `data-controller` attribute for the given controllers
|
11
|
-
def with_controllers(*controllers_to_set)
|
12
|
-
helpers.html_attributes("data-controller" => controller_list(controllers_to_set)&.html_safe)
|
13
|
-
end
|
14
|
-
|
15
|
-
# Return the HTML `data-target` attribute for the given targets
|
16
|
-
def as_targets(*targets)
|
17
|
-
attrs = build_target_data_attributes(parse_targets(targets))
|
18
|
-
helpers.html_attributes(attrs.transform_keys! { |k| "data-#{k}" })
|
19
|
-
end
|
20
|
-
alias_method :as_target, :as_targets
|
21
|
-
|
22
|
-
# Return the HTML `data-action` attribute for the given actions
|
23
|
-
def with_actions(*actions_to_set)
|
24
|
-
actions_str = action_list(actions_to_set)
|
25
|
-
actions_str.present? ? helpers.html_attributes("data-action" => actions_str) : nil
|
26
|
-
end
|
27
|
-
alias_method :with_action, :with_actions
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
# Complete list of actions ready to be use in the data-action attribute
|
32
|
-
def action_list(actions_to_parse)
|
33
|
-
return nil unless actions_to_parse&.size&.positive?
|
34
|
-
# `html_attributes` will escape '->' thus breaking the stimulus action, so we need to do our own escaping
|
35
|
-
actions_str_raw = parse_actions(actions_to_parse).join(" ")
|
36
|
-
CGI.escapeHTML(actions_str_raw).gsub("->", "->").html_safe
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
if Gem.loaded_specs.has_key? "phlex"
|
4
|
-
require "phlex"
|
5
|
-
|
6
|
-
module Vident
|
7
|
-
module RootComponent
|
8
|
-
class UsingPhlexHTML < Phlex::HTML
|
9
|
-
include Base
|
10
|
-
if Gem.loaded_specs.has_key? "better_html"
|
11
|
-
include UsingBetterHTML
|
12
|
-
end
|
13
|
-
|
14
|
-
STANDARD_ELEMENTS = [:a, :abbr, :address, :article, :aside, :b, :bdi, :bdo, :blockquote, :body, :button, :caption, :cite, :code, :colgroup, :data, :datalist, :dd, :del, :details, :dfn, :dialog, :div, :dl, :dt, :em, :fieldset, :figcaption, :figure, :footer, :form, :g, :h1, :h2, :h3, :h4, :h5, :h6, :head, :header, :hgroup, :html, :i, :iframe, :ins, :kbd, :label, :legend, :li, :main, :map, :mark, :menuitem, :meter, :nav, :noscript, :object, :ol, :optgroup, :option, :output, :p, :path, :picture, :pre, :progress, :q, :rp, :rt, :ruby, :s, :samp, :script, :section, :select, :slot, :small, :span, :strong, :style, :sub, :summary, :sup, :svg, :table, :tbody, :td, :template_tag, :textarea, :tfoot, :th, :thead, :time, :title, :tr, :u, :ul, :video, :wbr].freeze
|
15
|
-
VOID_ELEMENTS = [:area, :br, :embed, :hr, :img, :input, :link, :meta, :param, :source, :track, :col].freeze
|
16
|
-
|
17
|
-
VALID_TAGS = Set[*(STANDARD_ELEMENTS + VOID_ELEMENTS)].freeze
|
18
|
-
|
19
|
-
# Create a tag for a target with a block containing content
|
20
|
-
def target_tag(tag_name, targets, **options, &block)
|
21
|
-
parsed = parse_targets(Array.wrap(targets))
|
22
|
-
options[:data] ||= {}
|
23
|
-
options[:data].merge!(build_target_data_attributes(parsed))
|
24
|
-
generate_tag(tag_name, **options, &block)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Build a tag with the attributes determined by this components properties and stimulus
|
28
|
-
# data attributes.
|
29
|
-
def template(&block)
|
30
|
-
# Generate tag options and render
|
31
|
-
tag_type = @element_tag.presence&.to_sym || :div
|
32
|
-
raise ArgumentError, "Unsupported HTML tag name #{tag_type}" unless VALID_TAGS.include?(tag_type)
|
33
|
-
options = @html_options&.dup || {}
|
34
|
-
data_attrs = tag_data_attributes
|
35
|
-
data_attrs = options[:data].present? ? data_attrs.merge(options[:data]) : data_attrs
|
36
|
-
options = options.merge(id: @id) if @id
|
37
|
-
options.except!(:data)
|
38
|
-
options.merge!(data_attrs.transform_keys { |k| "data-#{k}" })
|
39
|
-
generate_tag(tag_type, **options, &block)
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def generate_tag(tag_type, **options, &block)
|
45
|
-
send((tag_type == :template) ? :template_tag : tag_type, **options, &block)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
if Gem.loaded_specs.has_key? "view_component"
|
4
|
-
require "view_component"
|
5
|
-
|
6
|
-
module Vident
|
7
|
-
module RootComponent
|
8
|
-
class UsingViewComponent < ::ViewComponent::Base
|
9
|
-
include Base
|
10
|
-
if Gem.loaded_specs.has_key? "better_html"
|
11
|
-
include UsingBetterHTML
|
12
|
-
end
|
13
|
-
|
14
|
-
SELF_CLOSING_TAGS = Set[:area, :base, :br, :col, :embed, :hr, :img, :input, :link, :meta, :param, :source, :track, :wbr].freeze
|
15
|
-
|
16
|
-
def target_tag(tag_name, targets, **options, &block)
|
17
|
-
parsed = parse_targets(Array.wrap(targets))
|
18
|
-
options[:data] ||= {}
|
19
|
-
options[:data].merge!(build_target_data_attributes(parsed))
|
20
|
-
content = view_context.capture(&block) if block
|
21
|
-
view_context.content_tag(tag_name, content, options)
|
22
|
-
end
|
23
|
-
|
24
|
-
def call
|
25
|
-
# Generate outer tag options and render
|
26
|
-
tag_type = content_tag_type
|
27
|
-
options = content_tag_options
|
28
|
-
if SELF_CLOSING_TAGS.include?(tag_type)
|
29
|
-
view_context.tag(tag_type, options)
|
30
|
-
else
|
31
|
-
view_context.content_tag(tag_type, content, options)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def content_tag_options
|
38
|
-
options = @html_options&.dup || {}
|
39
|
-
data_attrs = tag_data_attributes
|
40
|
-
options[:data] = options[:data].present? ? data_attrs.merge(options[:data]) : data_attrs
|
41
|
-
return options unless @id
|
42
|
-
options.merge(id: @id)
|
43
|
-
end
|
44
|
-
|
45
|
-
def content_tag_type
|
46
|
-
@element_tag.presence || :div
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
data/lib/vident/tailwind.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
|
4
|
-
module Vident
|
5
|
-
module Tailwind
|
6
|
-
# Adds a utility class merge specifically for Tailwind, allowing you to more easily specify class overrides
|
7
|
-
# without having to worry about the specificity of the classes.
|
8
|
-
def produce_style_classes(class_names)
|
9
|
-
::TailwindMerge::Merger.new.merge(dedupe_view_component_classes(class_names))
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
data/lib/vident/test_case.rb
DELETED
@@ -1,176 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Vident
|
4
|
-
module Testing
|
5
|
-
class AttributesTester
|
6
|
-
def initialize(test_configurations)
|
7
|
-
@test_configurations = test_configurations
|
8
|
-
end
|
9
|
-
|
10
|
-
# Generates attribute hashes for all permutations of the given valid values.
|
11
|
-
def valid_configurations
|
12
|
-
# Expands any auto generated values and returns all attributes and their values
|
13
|
-
test_attrs = prepare_attributes_to_test
|
14
|
-
|
15
|
-
# The first permutation is the initial state
|
16
|
-
initial_state = prepare_initial_test_state(test_attrs)
|
17
|
-
|
18
|
-
# Create remaining permutations
|
19
|
-
test_attrs.flat_map do |attr_name, values|
|
20
|
-
values.map { |v| initial_state.merge(attr_name => v) }
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# Generates attribute hashes for all permutations of the given invalid values.
|
25
|
-
def invalid_configurations
|
26
|
-
return [] unless invalid_configured?
|
27
|
-
|
28
|
-
# prepare a valid initial state, then add any attrs that have :invalid
|
29
|
-
initial_state = prepare_initial_test_state(prepare_attributes_to_test)
|
30
|
-
|
31
|
-
# Merge in the invalid permutations
|
32
|
-
test_configurations.inject([]) do |memo, attr|
|
33
|
-
key, opts = attr
|
34
|
-
next memo += nil if opts == :strict_boolean
|
35
|
-
if opts.is_a?(Hash)
|
36
|
-
values = if opts[:invalid].nil? && opts[:valid].is_a?(Hash)
|
37
|
-
# If no invalid key specified we generate based on whats valid
|
38
|
-
config = invalid_attribute_test_values_for(opts[:valid][:type], opts[:valid])
|
39
|
-
(config&.fetch(:invalid, []) || []) + (config&.fetch(:converts, []) || [])
|
40
|
-
elsif opts[:invalid].is_a?(Array)
|
41
|
-
opts[:invalid]
|
42
|
-
end
|
43
|
-
|
44
|
-
memo += values.map { |i| initial_state.merge(key => i) } if values
|
45
|
-
end
|
46
|
-
memo
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
attr_reader :test_configurations
|
53
|
-
|
54
|
-
def invalid_configured?
|
55
|
-
test_configurations.values.any? { |v| v.respond_to?(:key?) && v.key?(:invalid) }
|
56
|
-
end
|
57
|
-
|
58
|
-
def prepare_attributes_to_test
|
59
|
-
test_configurations.transform_values do |attr_config|
|
60
|
-
next [true, false, nil] if attr_config == :boolean
|
61
|
-
next [true, false] if attr_config == :strict_boolean
|
62
|
-
valid = attr_config[:valid]
|
63
|
-
raise "Ensure :valid attributes configuration is provided" unless valid
|
64
|
-
next valid if valid.is_a?(Array)
|
65
|
-
attribute_test_values_for(valid)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def prepare_initial_test_state(test_attrs)
|
70
|
-
initial_state = {}
|
71
|
-
test_attrs.each { |attr_name, values| initial_state[attr_name] = values.first }
|
72
|
-
initial_state
|
73
|
-
end
|
74
|
-
|
75
|
-
def attribute_test_values_for(options)
|
76
|
-
type = parse_type(options[:type])
|
77
|
-
return options[:in] if options[:in]
|
78
|
-
values =
|
79
|
-
case type
|
80
|
-
when :string, "String"
|
81
|
-
s = (1..8).map { |l| ::Faker::String.random(length: l) }
|
82
|
-
s.prepend "test string"
|
83
|
-
s = s.select(&:present?)
|
84
|
-
s << "" if options[:allow_blank]
|
85
|
-
s
|
86
|
-
when :boolean
|
87
|
-
[false, true]
|
88
|
-
when :float, "Float"
|
89
|
-
(1..3).map { Faker::Number.positive } + (1..3).map { Faker::Number.negative }
|
90
|
-
when :numeric, "Numeric"
|
91
|
-
(1..3).map { Faker::Number.positive } + [1, 5]
|
92
|
-
when :integer, "Integer"
|
93
|
-
min = options[:min] || -10_000
|
94
|
-
max = options[:max] || 10_000
|
95
|
-
(1..3).map { Kernel.rand(min..max) }
|
96
|
-
when :array, "Array"
|
97
|
-
a =
|
98
|
-
if options[:sub_type] == Numeric
|
99
|
-
[[1, 2, 3], [0.3, 2, 0.002]]
|
100
|
-
elsif options[:sub_type]
|
101
|
-
[[options[:sub_type].new]]
|
102
|
-
else
|
103
|
-
[%i[a b c], [1, 2, 3], %w[a b]]
|
104
|
-
end
|
105
|
-
a << [] if options[:allow_blank]
|
106
|
-
a
|
107
|
-
when :any
|
108
|
-
[false, 1245, {}, :df, "hi"]
|
109
|
-
when :hash, "Hash"
|
110
|
-
a = [{a: 1}]
|
111
|
-
a << {} if options[:allow_blank]
|
112
|
-
a
|
113
|
-
when :symbol, "Symbol"
|
114
|
-
%i[a b c]
|
115
|
-
else
|
116
|
-
raise StandardError, "Attribute type not understood (#{type})"
|
117
|
-
end
|
118
|
-
|
119
|
-
if options[:allow_nil] || !options[:default].nil? || (options[:allow_blank] && options[:allow_nil].nil?)
|
120
|
-
values << nil
|
121
|
-
end
|
122
|
-
values
|
123
|
-
end
|
124
|
-
|
125
|
-
def invalid_attribute_test_values_for(type, options)
|
126
|
-
values = case parse_type(type)
|
127
|
-
when :string, "String"
|
128
|
-
# All values are also convertable to string
|
129
|
-
a = {converts: [false, 1245, 1.0, {}, :df, []], invalid: []}
|
130
|
-
a[:invalid] << "" if options[:allow_blank] == false
|
131
|
-
a
|
132
|
-
when :boolean
|
133
|
-
# All values are also convertable to boolean with !!
|
134
|
-
{converts: ["sdf", 234, 3.5, {}, :sdf, []], invalid: []}
|
135
|
-
when :float, "Float"
|
136
|
-
# Not all values are convertable to float
|
137
|
-
{converts: [234, "12.2"], invalid: ["sdf", 234, {}, :sdf, [], false]}
|
138
|
-
when :numeric, "Numeric"
|
139
|
-
{converts: ["12.2"], invalid: ["sdf", {}, :sdf, [], false]}
|
140
|
-
when :integer, "Integer"
|
141
|
-
# Not all values are convertable to integer
|
142
|
-
{converts: [234.0, "123", "sdf"], invalid: [{}, :sdf, [], false]}
|
143
|
-
when :array, "Array"
|
144
|
-
# Not all values are convertable to array
|
145
|
-
a = if options[:sub_type]
|
146
|
-
{converts: [{}, [{}]], invalid: ["sdf", [123], [Class.new]]}
|
147
|
-
else
|
148
|
-
{converts: [{}], invalid: ["sdf", 234, 3.5, :sdf, false]}
|
149
|
-
end
|
150
|
-
a[:invalid] << [] if options[:allow_blank] == false
|
151
|
-
a
|
152
|
-
when :any
|
153
|
-
# There are no invalid values
|
154
|
-
{converts: [], invalid: []}
|
155
|
-
when :hash, "Hash"
|
156
|
-
a = {converts: [[], [[:a, 1], [:b, 2]]], invalid: [:sdf, false, "boo", 123]}
|
157
|
-
a[:invalid] << {} if options[:allow_blank] == false
|
158
|
-
a
|
159
|
-
when :symbol, "Symbol"
|
160
|
-
{converts: ["foo"], invalid: [{}, false, [], 123]}
|
161
|
-
else
|
162
|
-
raise StandardError, "Attribute type not understood (#{type})"
|
163
|
-
end
|
164
|
-
|
165
|
-
if options[:default].nil? && (!options[:allow_nil] || (!options[:allow_blank] && options[:allow_nil] != true))
|
166
|
-
values[:invalid] << nil
|
167
|
-
end
|
168
|
-
values
|
169
|
-
end
|
170
|
-
|
171
|
-
def parse_type(type)
|
172
|
-
type.is_a?(Symbol) ? type : type.name
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "minitest/hooks"
|
4
|
-
|
5
|
-
module Vident
|
6
|
-
module Testing
|
7
|
-
module AutoTest
|
8
|
-
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
included do
|
11
|
-
include Minitest::Hooks
|
12
|
-
|
13
|
-
def before_all
|
14
|
-
@results_content = []
|
15
|
-
::Vident::StableId.set_current_sequence_generator
|
16
|
-
end
|
17
|
-
|
18
|
-
def after_all
|
19
|
-
html = <<~HTML
|
20
|
-
<!doctype html>
|
21
|
-
<html lang="en">
|
22
|
-
<head>
|
23
|
-
<meta charset="UTF-8">
|
24
|
-
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
25
|
-
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
26
|
-
<title>Test run output</title>
|
27
|
-
</head>
|
28
|
-
<body>
|
29
|
-
#{@results_content.map(&:to_s).join("<hr>\n").html_safe}
|
30
|
-
</body>
|
31
|
-
</html>
|
32
|
-
HTML
|
33
|
-
|
34
|
-
# TODO: configurable layout
|
35
|
-
filename = self.class.name.gsub("::", "_")
|
36
|
-
File.write("render_results_#{filename}.html", html) # TODO: path for outputs (eg default tmp/)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class_methods do
|
41
|
-
def auto_test(class_under_test, **param_config)
|
42
|
-
attribute_tester = Vident::Testing::AttributesTester.new(param_config)
|
43
|
-
attribute_tester.valid_configurations.each_with_index do |test, index|
|
44
|
-
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
45
|
-
def test_renders_with_valid_attrs_#{index}
|
46
|
-
test_attrs = #{test}
|
47
|
-
begin
|
48
|
-
@results_content << render_inline(#{class_under_test}.new(**test_attrs))
|
49
|
-
rescue => error
|
50
|
-
assert(false, "Should not raise with #{test.to_s.tr("\"", "'")} but did raise \#{error}")
|
51
|
-
end
|
52
|
-
end
|
53
|
-
RUBY
|
54
|
-
end
|
55
|
-
|
56
|
-
attribute_tester.invalid_configurations.each_with_index do |test, index|
|
57
|
-
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
58
|
-
def test_raises_with_invalid_attrs_#{index}
|
59
|
-
test_attrs = #{test}
|
60
|
-
assert_raises(StandardError, "Should raise with #{test.to_s.tr("\"", "'")}") do
|
61
|
-
@results_content << render_inline(#{class_under_test}.new(**test_attrs))
|
62
|
-
end
|
63
|
-
end
|
64
|
-
RUBY
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|