vident 0.7.0 → 0.9.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 +103 -418
- 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/caching.rb +120 -0
- 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 +12 -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
@@ -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
|