vident-typed-minitest 0.13.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.
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vident
4
+ module Typed
5
+ module Minitest
6
+ class AttributesTester
7
+ def initialize(test_configurations)
8
+ @test_configurations = test_configurations
9
+ end
10
+
11
+ # Generates attribute hashes for all permutations of the given valid values.
12
+ def valid_configurations
13
+ # Expands any auto generated values and returns all attributes and their values
14
+ test_attrs = prepare_attributes_to_test
15
+
16
+ # The first permutation is the initial state
17
+ initial_state = prepare_initial_test_state(test_attrs)
18
+
19
+ # Create remaining permutations
20
+ test_attrs.flat_map do |attr_name, values|
21
+ values.map { |v| initial_state.merge(attr_name => v) }
22
+ end
23
+ end
24
+
25
+ # Generates attribute hashes for all permutations of the given invalid values.
26
+ def invalid_configurations
27
+ return [] unless invalid_configured?
28
+
29
+ # prepare a valid initial state, then add any attrs that have :invalid
30
+ initial_state = prepare_initial_test_state(prepare_attributes_to_test)
31
+
32
+ # Merge in the invalid permutations
33
+ test_configurations.inject([]) do |memo, attr|
34
+ key, opts = attr
35
+ next memo += nil if opts == :strict_boolean
36
+ if opts.is_a?(Hash)
37
+ values = if opts[:invalid].nil? && opts[:valid].is_a?(Hash)
38
+ # If no invalid key specified we generate based on whats valid
39
+ config = invalid_attribute_test_values_for(opts[:valid][:type], opts[:valid])
40
+ (config&.fetch(:invalid, []) || []) + (config&.fetch(:converts, []) || [])
41
+ elsif opts[:invalid].is_a?(Array)
42
+ opts[:invalid]
43
+ end
44
+
45
+ memo += values.map { |i| initial_state.merge(key => i) } if values
46
+ end
47
+ memo
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :test_configurations
54
+
55
+ def invalid_configured?
56
+ test_configurations.values.any? { |v| v.respond_to?(:key?) && v.key?(:invalid) }
57
+ end
58
+
59
+ def prepare_attributes_to_test
60
+ test_configurations.transform_values do |attr_config|
61
+ next [true, false, nil] if attr_config == :boolean
62
+ next [true, false] if attr_config == :strict_boolean
63
+ valid = attr_config[:valid]
64
+ raise "Ensure :valid attributes configuration is provided" unless valid
65
+ next valid if valid.is_a?(Array)
66
+ attribute_test_values_for(valid)
67
+ end
68
+ end
69
+
70
+ def prepare_initial_test_state(test_attrs)
71
+ initial_state = {}
72
+ test_attrs.each { |attr_name, values| initial_state[attr_name] = values.first }
73
+ initial_state
74
+ end
75
+
76
+ def attribute_test_values_for(options)
77
+ type = parse_type(options[:type])
78
+ return options[:in] if options[:in]
79
+ values =
80
+ case type
81
+ when :string, "String"
82
+ s = (1..8).map { |l| ::Faker::String.random(length: l) }
83
+ s.prepend "test string"
84
+ s = s.select(&:present?)
85
+ s << "" if options[:allow_blank]
86
+ s
87
+ when :boolean
88
+ [false, true]
89
+ when :float, "Float"
90
+ (1..3).map { Faker::Number.positive } + (1..3).map { Faker::Number.negative }
91
+ when :numeric, "Numeric"
92
+ (1..3).map { Faker::Number.positive } + [1, 5]
93
+ when :integer, "Integer"
94
+ min = options[:min] || -10_000
95
+ max = options[:max] || 10_000
96
+ (1..3).map { Kernel.rand(min..max) }
97
+ when :array, "Array"
98
+ a =
99
+ if options[:sub_type] == Numeric
100
+ [[1, 2, 3], [0.3, 2, 0.002]]
101
+ elsif options[:sub_type]
102
+ [[options[:sub_type].new]]
103
+ else
104
+ [%i[a b c], [1, 2, 3], %w[a b]]
105
+ end
106
+ a << [] if options[:allow_blank]
107
+ a
108
+ when :any
109
+ [false, 1245, {}, :df, "hi"]
110
+ when :hash, "Hash"
111
+ a = [{a: 1}]
112
+ a << {} if options[:allow_blank]
113
+ a
114
+ when :symbol, "Symbol"
115
+ %i[a b c]
116
+ else
117
+ raise StandardError, "Attribute type not understood (#{type})"
118
+ end
119
+
120
+ if options[:allow_nil] || !options[:default].nil? || (options[:allow_blank] && options[:allow_nil].nil?)
121
+ values << nil
122
+ end
123
+ values
124
+ end
125
+
126
+ def invalid_attribute_test_values_for(type, options)
127
+ values = case parse_type(type)
128
+ when :string, "String"
129
+ # All values are also convertable to string
130
+ string_values(options)
131
+ when :boolean
132
+ # All values are also convertable to boolean with !!
133
+ boolean_values
134
+ when :float, "Float"
135
+ # Not all values are convertable to float
136
+ float_values
137
+ when :numeric, "Numeric"
138
+ numeric_values
139
+ when :integer, "Integer"
140
+ # Not all values are convertable to integer
141
+ integer_values
142
+ when :array, "Array"
143
+ # Not all values are convertable to array
144
+ array_values(options)
145
+ when :any
146
+ # There are no invalid values
147
+ any_values
148
+ when :hash, "Hash"
149
+ hash_values(options)
150
+ when :symbol, "Symbol"
151
+ symbol_values
152
+ else
153
+ raise StandardError, "Attribute type not understood (#{type})"
154
+ end
155
+
156
+ if options[:default].nil? && (!options[:allow_nil] || (!options[:allow_blank] && options[:allow_nil] != true))
157
+ values[:invalid] << nil
158
+ end
159
+ values
160
+ end
161
+
162
+ def parse_type(type)
163
+ type.is_a?(Symbol) ? type : type.name
164
+ end
165
+
166
+ def string_values(options)
167
+ a = {converts: [false, 1245, 1.0, {}, :df, []], invalid: []}
168
+ a[:invalid] << "" if options[:allow_blank] == false
169
+ a
170
+ end
171
+
172
+ def boolean_values
173
+ {converts: ["sdf", 234, 3.5, {}, :sdf, []], invalid: []}
174
+ end
175
+
176
+ def float_values
177
+ {converts: [234, "12.2"], invalid: ["sdf", 234, {}, :sdf, [], false]}
178
+ end
179
+
180
+ def numeric_values
181
+ {converts: ["12.2"], invalid: ["sdf", {}, :sdf, [], false]}
182
+ end
183
+
184
+ def integer_values
185
+ {converts: [234.0, "123", "sdf"], invalid: [{}, :sdf, [], false]}
186
+ end
187
+
188
+ def array_values(options)
189
+ a = if options[:sub_type]
190
+ {converts: [{}, [{}]], invalid: ["sdf", [123], [Class.new]]}
191
+ else
192
+ {converts: [{}], invalid: ["sdf", 234, 3.5, :sdf, false]}
193
+ end
194
+ a[:invalid] << [] if options[:allow_blank] == false
195
+ a
196
+ end
197
+
198
+ def any_values
199
+ {converts: [], invalid: []}
200
+ end
201
+
202
+ def hash_values(options)
203
+ a = {converts: [[], [[:a, 1], [:b, 2]]], invalid: [:sdf, false, "boo", 123]}
204
+ a[:invalid] << {} if options[:allow_blank] == false
205
+ a
206
+ end
207
+
208
+ def symbol_values
209
+ {converts: ["foo"], invalid: [{}, false, [], 123]}
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/hooks"
4
+
5
+ module Vident
6
+ module Typed
7
+ module Minitest
8
+ module AutoTest
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include ::Minitest::Hooks
13
+
14
+ def before_all
15
+ @results_content = []
16
+ ::Vident::StableId.set_current_sequence_generator
17
+ end
18
+
19
+ def after_all
20
+ html = <<~HTML
21
+ <!doctype html>
22
+ <html lang="en">
23
+ <head>
24
+ <meta charset="UTF-8">
25
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
26
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
27
+ <title>Test run output</title>
28
+ </head>
29
+ <body>
30
+ #{@results_content.map(&:to_s).join("<hr>\n").html_safe}
31
+ </body>
32
+ </html>
33
+ HTML
34
+
35
+ # TODO: configurable layout
36
+ filename = self.class.name.gsub("::", "_")
37
+ File.write("render_results_#{filename}.html", html) # TODO: path for outputs (eg default tmp/)
38
+ end
39
+ end
40
+
41
+ class_methods do
42
+ def auto_test(class_under_test, **param_config)
43
+ attribute_tester = AttributesTester.new(param_config)
44
+ attribute_tester.valid_configurations.each_with_index do |test, index|
45
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
46
+ def test_renders_with_valid_attrs_#{index}
47
+ test_attrs = #{test}
48
+ begin
49
+ @results_content << render_inline(#{class_under_test}.new(**test_attrs))
50
+ assert @results_content.present?, "Should render with #{test.to_s.tr("\"", "'")}"
51
+ rescue => error
52
+ assert(false, "Should not raise with #{test.to_s.tr("\"", "'")} but did raise \#{error}")
53
+ end
54
+ end
55
+ RUBY
56
+ end
57
+
58
+ attribute_tester.invalid_configurations.each_with_index do |test, index|
59
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
60
+ def test_raises_with_invalid_attrs_#{index}
61
+ test_attrs = #{test}
62
+ assert_raises(StandardError, "Should raise with #{test.to_s.tr("\"", "'")}") do
63
+ @results_content << render_inline(#{class_under_test}.new(**test_attrs))
64
+ end
65
+ end
66
+ RUBY
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,8 @@
1
+ module Vident
2
+ module Typed
3
+ module Minitest
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: what about when used with Phlex?
4
+ module Vident
5
+ module Typed
6
+ module Minitest
7
+ class TestCase < ::ViewComponent::TestCase
8
+ include AutoTest
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ module Vident
2
+ module Typed
3
+ module Minitest
4
+ VERSION = Vident::VERSION
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require "vident/typed"
2
+ require "vident/typed/minitest/version"
3
+ require "vident/typed/minitest/attributes_tester"
4
+ require "vident/typed/minitest/auto_test"
5
+ require "vident/typed/minitest/test_case"
6
+ require "vident/typed/minitest/engine" if defined?(Rails)
7
+
8
+ module Vident
9
+ module Typed
10
+ module Minitest
11
+ # Your code goes here...
12
+ end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vident-typed-minitest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.13.0
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Ierodiaconou
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-04-07 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: activesupport
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.2'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '9'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '7.2'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '9'
52
+ - !ruby/object:Gem::Dependency
53
+ name: vident-typed
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: 0.13.0
59
+ type: :runtime
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: 0.13.0
66
+ - !ruby/object:Gem::Dependency
67
+ name: minitest
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 5.14.4
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 5.14.4
83
+ - - "<"
84
+ - !ruby/object:Gem::Version
85
+ version: '6.0'
86
+ - !ruby/object:Gem::Dependency
87
+ name: minitest-hooks
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 1.5.0
93
+ - - "<"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.0'
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 1.5.0
103
+ - - "<"
104
+ - !ruby/object:Gem::Version
105
+ version: '2.0'
106
+ - !ruby/object:Gem::Dependency
107
+ name: faker
108
+ requirement: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 2.22.0
113
+ - - "<"
114
+ - !ruby/object:Gem::Version
115
+ version: '4.0'
116
+ type: :runtime
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 2.22.0
123
+ - - "<"
124
+ - !ruby/object:Gem::Version
125
+ version: '4.0'
126
+ description: Vident test helper for Minitest
127
+ email:
128
+ - stevegeek@gmail.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - CHANGELOG.md
134
+ - LICENSE.txt
135
+ - README.md
136
+ - lib/vident/typed/minitest.rb
137
+ - lib/vident/typed/minitest/attributes_tester.rb
138
+ - lib/vident/typed/minitest/auto_test.rb
139
+ - lib/vident/typed/minitest/engine.rb
140
+ - lib/vident/typed/minitest/test_case.rb
141
+ - lib/vident/typed/minitest/version.rb
142
+ homepage: https://github.com/stevegeek/vident
143
+ licenses:
144
+ - MIT
145
+ metadata:
146
+ homepage_uri: https://github.com/stevegeek/vident
147
+ source_code_uri: https://github.com/stevegeek/vident
148
+ changelog_uri: https://github.com/stevegeek/vident/blob/main/CHANGELOG.md
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: 3.1.0
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubygems_version: 3.6.2
164
+ specification_version: 4
165
+ summary: Vident test helper for Minitest
166
+ test_files: []