spec_forge 0.4.0 → 0.6.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/.standard.yml +4 -0
- data/CHANGELOG.md +145 -1
- data/README.md +49 -638
- data/flake.lock +3 -3
- data/flake.nix +8 -2
- data/lib/spec_forge/attribute/chainable.rb +208 -20
- data/lib/spec_forge/attribute/factory.rb +141 -12
- data/lib/spec_forge/attribute/faker.rb +64 -15
- data/lib/spec_forge/attribute/global.rb +96 -0
- data/lib/spec_forge/attribute/literal.rb +15 -2
- data/lib/spec_forge/attribute/matcher.rb +188 -13
- data/lib/spec_forge/attribute/parameterized.rb +45 -20
- data/lib/spec_forge/attribute/regex.rb +55 -5
- data/lib/spec_forge/attribute/resolvable.rb +48 -5
- data/lib/spec_forge/attribute/resolvable_array.rb +62 -4
- data/lib/spec_forge/attribute/resolvable_hash.rb +62 -4
- data/lib/spec_forge/attribute/store.rb +65 -0
- data/lib/spec_forge/attribute/transform.rb +33 -5
- data/lib/spec_forge/attribute/variable.rb +37 -6
- data/lib/spec_forge/attribute.rb +168 -66
- data/lib/spec_forge/backtrace_formatter.rb +26 -3
- data/lib/spec_forge/callbacks.rb +79 -0
- data/lib/spec_forge/cli/actions.rb +27 -0
- data/lib/spec_forge/cli/command.rb +78 -24
- data/lib/spec_forge/cli/init.rb +11 -1
- data/lib/spec_forge/cli/new.rb +54 -3
- data/lib/spec_forge/cli/run.rb +20 -0
- data/lib/spec_forge/cli.rb +16 -5
- data/lib/spec_forge/configuration.rb +94 -25
- data/lib/spec_forge/context/callbacks.rb +91 -0
- data/lib/spec_forge/context/global.rb +72 -0
- data/lib/spec_forge/context/store.rb +148 -0
- data/lib/spec_forge/context/variables.rb +91 -0
- data/lib/spec_forge/context.rb +36 -0
- data/lib/spec_forge/core_ext/rspec.rb +24 -4
- data/lib/spec_forge/error.rb +267 -113
- data/lib/spec_forge/factory.rb +33 -14
- data/lib/spec_forge/filter.rb +87 -0
- data/lib/spec_forge/forge.rb +170 -0
- data/lib/spec_forge/http/backend.rb +99 -29
- data/lib/spec_forge/http/client.rb +23 -13
- data/lib/spec_forge/http/request.rb +74 -62
- data/lib/spec_forge/http/verb.rb +79 -0
- data/lib/spec_forge/http.rb +105 -0
- data/lib/spec_forge/loader.rb +254 -0
- data/lib/spec_forge/matchers.rb +130 -0
- data/lib/spec_forge/normalizer/configuration.rb +24 -11
- data/lib/spec_forge/normalizer/constraint.rb +22 -9
- data/lib/spec_forge/normalizer/expectation.rb +31 -12
- data/lib/spec_forge/normalizer/factory.rb +24 -11
- data/lib/spec_forge/normalizer/factory_reference.rb +32 -13
- data/lib/spec_forge/normalizer/global_context.rb +88 -0
- data/lib/spec_forge/normalizer/spec.rb +39 -16
- data/lib/spec_forge/normalizer.rb +255 -41
- data/lib/spec_forge/runner/callbacks.rb +246 -0
- data/lib/spec_forge/runner/debug_proxy.rb +213 -0
- data/lib/spec_forge/runner/listener.rb +54 -0
- data/lib/spec_forge/runner/metadata.rb +58 -0
- data/lib/spec_forge/runner/state.rb +99 -0
- data/lib/spec_forge/runner.rb +133 -119
- data/lib/spec_forge/spec/expectation/constraint.rb +95 -20
- data/lib/spec_forge/spec/expectation.rb +43 -51
- data/lib/spec_forge/spec.rb +83 -96
- data/lib/spec_forge/type.rb +36 -4
- data/lib/spec_forge/version.rb +4 -1
- data/lib/spec_forge.rb +161 -76
- metadata +20 -5
- data/spec_forge/factories/user.yml +0 -4
- data/spec_forge/forge_helper.rb +0 -37
- data/spec_forge/specs/users.yml +0 -65
@@ -4,39 +4,114 @@ module SpecForge
|
|
4
4
|
class Spec
|
5
5
|
class Expectation
|
6
6
|
#
|
7
|
-
# Represents the
|
7
|
+
# Represents the expected response constraints for an expectation
|
8
|
+
#
|
9
|
+
# A Constraint defines what the API response should look like,
|
10
|
+
# including status code and body content with support for matchers.
|
11
|
+
#
|
12
|
+
# @example In code
|
13
|
+
# constraint = Constraint.new(
|
14
|
+
# status: 200,
|
15
|
+
# json: {name: "matcher.eq" => "John"}
|
16
|
+
# )
|
8
17
|
#
|
9
18
|
class Constraint < Data.define(:status, :json) # :xml, :html
|
10
19
|
#
|
11
|
-
# Creates a new
|
20
|
+
# Creates a new constraint
|
12
21
|
#
|
13
|
-
# @param status [Integer] The expected HTTP status code
|
14
|
-
# @param json [Hash] The expected JSON with matchers
|
22
|
+
# @param status [Integer, String] The expected HTTP status code, or reference to one
|
23
|
+
# @param json [Hash, Array] The expected JSON with matchers
|
15
24
|
#
|
16
|
-
|
17
|
-
|
25
|
+
# @return [Constraint] A new constraint instance
|
26
|
+
#
|
27
|
+
def initialize(status:, json: {})
|
28
|
+
super(
|
29
|
+
status: Attribute.from(status),
|
30
|
+
json: Attribute.from(json)
|
31
|
+
)
|
18
32
|
end
|
19
33
|
|
34
|
+
#
|
35
|
+
# Converts the constraint to a hash with resolved values
|
36
|
+
#
|
37
|
+
# @return [Hash] Hash representation with resolved values
|
38
|
+
#
|
20
39
|
def to_h
|
21
40
|
super.transform_values(&:resolve)
|
22
41
|
end
|
23
42
|
|
43
|
+
#
|
44
|
+
# Converts constraints to RSpec matchers for validation
|
45
|
+
#
|
46
|
+
# Transforms the defined constraints (status and JSON expectations) into
|
47
|
+
# appropriate RSpec matchers that can be used in test expectations.
|
48
|
+
# This method resolves all values and applies the appropriate matcher
|
49
|
+
# conversions to create a complete expectation structure.
|
50
|
+
#
|
51
|
+
# @return [Hash] A hash containing resolved matchers
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# constraint = Constraint.new(status: 200, json: {name: "John"})
|
55
|
+
# matchers = constraint.as_matchers
|
56
|
+
# # => {status: eq(200), json: include("name" => eq("John"))}
|
57
|
+
#
|
58
|
+
def as_matchers
|
59
|
+
{
|
60
|
+
status: status.resolve_as_matcher,
|
61
|
+
json: resolve_json_matcher
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Generates a human-readable description of what this constraint expects in the response
|
67
|
+
#
|
68
|
+
# Creates a description string for RSpec examples that clearly explains the expected
|
69
|
+
# status code and JSON structure. This makes test output more informative and helps
|
70
|
+
# developers understand what's being tested at a glance.
|
71
|
+
#
|
72
|
+
# @return [String] A human-readable description of the constraint expectations
|
73
|
+
#
|
74
|
+
# @example Status code with JSON object
|
75
|
+
# constraint.description
|
76
|
+
# # => "is expected to respond with \"200 OK\" and a JSON object that contains keys: \"id\", \"name\""
|
77
|
+
#
|
78
|
+
# @example Status code with JSON array
|
79
|
+
# constraint.description
|
80
|
+
# # => "is expected to respond with \"201 Created\" and a JSON array that contains 3 items"
|
81
|
+
#
|
82
|
+
def description
|
83
|
+
description = "is expected to respond with"
|
84
|
+
|
85
|
+
description += if status.is_a?(Attribute::Literal)
|
86
|
+
" #{HTTP.status_code_to_description(status.input).in_quotes}"
|
87
|
+
else
|
88
|
+
" the expected status code"
|
89
|
+
end
|
90
|
+
|
91
|
+
size = json.size
|
92
|
+
|
93
|
+
if Type.array?(json)
|
94
|
+
description +=
|
95
|
+
" and a JSON array that contains #{size} #{"item".pluralize(size)}"
|
96
|
+
elsif Type.hash?(json) && size > 0
|
97
|
+
keys = json.keys.join_map(", ", &:in_quotes)
|
98
|
+
|
99
|
+
description +=
|
100
|
+
" and a JSON object that contains #{"key".pluralize(size)}: #{keys}"
|
101
|
+
end
|
102
|
+
|
103
|
+
description
|
104
|
+
end
|
105
|
+
|
24
106
|
private
|
25
107
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
Attribute.from("matcher.eq" => attribute.resolve)
|
34
|
-
else
|
35
|
-
attribute
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
Attribute.from(hash)
|
108
|
+
def resolve_json_matcher
|
109
|
+
case json
|
110
|
+
when HashLike
|
111
|
+
json.transform_values(&:resolve_as_matcher).stringify_keys
|
112
|
+
else
|
113
|
+
json.resolve_as_matcher
|
114
|
+
end
|
40
115
|
end
|
41
116
|
end
|
42
117
|
end
|
@@ -1,72 +1,64 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "expectation/constraint"
|
4
|
-
|
5
3
|
module SpecForge
|
6
4
|
class Spec
|
7
|
-
|
5
|
+
#
|
6
|
+
# Represents a single test expectation within a spec
|
7
|
+
#
|
8
|
+
# An Expectation defines what should be tested for a specific API request,
|
9
|
+
# including the expected status code and response structure.
|
10
|
+
#
|
11
|
+
# @example YAML representation
|
12
|
+
# - name: "Get user successfully"
|
13
|
+
# expect:
|
14
|
+
# status: 200
|
15
|
+
# json:
|
16
|
+
# name: kind_of.string
|
17
|
+
#
|
18
|
+
class Expectation < Data.define(:id, :name, :line_number, :debug, :store_as, :constraints)
|
19
|
+
#
|
20
|
+
# @return [Boolean] True if debugging is enabled
|
21
|
+
#
|
8
22
|
attr_predicate :debug
|
9
23
|
|
10
|
-
|
24
|
+
#
|
25
|
+
# @return [Boolean] True if store_as is set
|
26
|
+
#
|
27
|
+
attr_predicate :store_as
|
11
28
|
|
12
29
|
#
|
13
|
-
# Creates a new
|
30
|
+
# Creates a new expectation with constraints
|
14
31
|
#
|
15
|
-
# @param
|
16
|
-
# @param name [String]
|
32
|
+
# @param id [String] Unique identifier
|
33
|
+
# @param name [String] Human-readable name
|
34
|
+
# @param line_number [Integer] Line number in source
|
35
|
+
# @param debug [Boolean] Whether to enable debugging
|
36
|
+
# @param store_as [String] Unique Context::Store identifier
|
37
|
+
# @param expect [Hash] Expected constraints
|
17
38
|
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
load_debug(input)
|
23
|
-
load_variables(input)
|
24
|
-
|
25
|
-
# Must be after load_variables
|
26
|
-
load_constraints(input)
|
27
|
-
|
28
|
-
@http_client = HTTP::Client.new(
|
29
|
-
variables:, **input.except(:name, :variables, :expect, :debug)
|
30
|
-
)
|
39
|
+
# @return [Expectation] A new expectation instance
|
40
|
+
#
|
41
|
+
def initialize(id:, name:, line_number:, debug:, store_as:, expect:)
|
42
|
+
constraints = Constraint.new(**expect)
|
31
43
|
|
32
|
-
|
33
|
-
load_name(input)
|
44
|
+
super(id:, name:, line_number:, debug:, store_as:, constraints:)
|
34
45
|
end
|
35
46
|
|
47
|
+
#
|
48
|
+
# Converts the expectation to a hash representation
|
49
|
+
#
|
50
|
+
# @return [Hash] Hash representation
|
51
|
+
#
|
36
52
|
def to_h
|
37
53
|
{
|
38
54
|
name:,
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
constraints: constraints.to_h
|
55
|
+
line_number:,
|
56
|
+
debug:,
|
57
|
+
expect: constraints.to_h
|
43
58
|
}
|
44
59
|
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def load_name(input)
|
49
|
-
# GET /users
|
50
|
-
@name = "#{http_client.request.http_verb.upcase} #{http_client.request.url}"
|
51
|
-
|
52
|
-
# GET /users - Returns a 404
|
53
|
-
if (name = input[:name].resolve.presence)
|
54
|
-
@name += " - #{name}"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def load_variables(input)
|
59
|
-
@variables = Attribute.bind_variables(input[:variables], input[:variables])
|
60
|
-
end
|
61
|
-
|
62
|
-
def load_debug(input)
|
63
|
-
@debug = input[:debug].resolve
|
64
|
-
end
|
65
|
-
|
66
|
-
def load_constraints(input)
|
67
|
-
constraints = Attribute.bind_variables(input[:expect], variables)
|
68
|
-
@constraints = Constraint.new(**constraints)
|
69
|
-
end
|
70
60
|
end
|
71
61
|
end
|
72
62
|
end
|
63
|
+
|
64
|
+
require_relative "expectation/constraint"
|
data/lib/spec_forge/spec.rb
CHANGED
@@ -1,126 +1,113 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "spec/expectation"
|
4
|
-
|
5
3
|
module SpecForge
|
4
|
+
#
|
5
|
+
# Represents a test specification in SpecForge
|
6
|
+
#
|
7
|
+
# A Spec contains one or more Expectations and defines the base configuration
|
8
|
+
# for those expectations. It maps directly to a test defined in YAML.
|
9
|
+
#
|
10
|
+
# @example YAML representation
|
11
|
+
# get_users:
|
12
|
+
# path: /users
|
13
|
+
# expectations:
|
14
|
+
# - expect:
|
15
|
+
# status: 200
|
16
|
+
#
|
6
17
|
class Spec
|
7
18
|
#
|
8
|
-
#
|
19
|
+
# @return [Boolean] True if debugging is enabled
|
9
20
|
#
|
10
|
-
|
11
|
-
|
12
|
-
# @param expectation_name [String, nil] The name of the expectation for a spec.
|
21
|
+
attr_predicate :debug
|
22
|
+
|
13
23
|
#
|
14
|
-
#
|
24
|
+
# Unique identifier for this spec
|
15
25
|
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
filter_specs(specs, file_name:, spec_name:, expectation_name:)
|
20
|
-
|
21
|
-
# Announce if we're using a filter
|
22
|
-
if file_name
|
23
|
-
filter = {file_name:, spec_name:, expectation_name:}.delete_if { |k, v| v.blank? }
|
24
|
-
filter.stringify_keys!
|
25
|
-
puts "Using filter: #{filter}"
|
26
|
-
end
|
27
|
-
|
28
|
-
specs.each(&:define)
|
29
|
-
end
|
26
|
+
# @return [String] The spec ID
|
27
|
+
#
|
28
|
+
attr_reader :id
|
30
29
|
|
31
30
|
#
|
32
|
-
#
|
31
|
+
# Human-readable name for this spec
|
33
32
|
#
|
34
|
-
# @return [
|
33
|
+
# @return [String] The spec name
|
35
34
|
#
|
36
|
-
|
37
|
-
path = SpecForge.forge.join("specs")
|
38
|
-
specs = []
|
39
|
-
|
40
|
-
Dir[path.join("**/*.yml")].each do |file_path|
|
41
|
-
content = File.read(file_path)
|
42
|
-
hash = YAML.load(content).deep_symbolize_keys
|
43
|
-
|
44
|
-
hash.each do |spec_name, spec_hash|
|
45
|
-
line_number = content.lines.index { |line| line.start_with?("#{spec_name}:") }
|
46
|
-
|
47
|
-
spec_hash[:name] = spec_name.to_s
|
48
|
-
spec_hash[:file_path] = file_path
|
49
|
-
spec_hash[:file_name] = file_path.delete_prefix("#{path}/").delete_suffix(".yml")
|
50
|
-
spec_hash[:line_number] = line_number ? line_number + 1 : -1
|
35
|
+
attr_reader :name
|
51
36
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# @private
|
60
|
-
def self.filter_specs(specs, file_name: nil, spec_name: nil, expectation_name: nil)
|
61
|
-
# Guard against invalid partial filters
|
62
|
-
if expectation_name && spec_name.blank?
|
63
|
-
raise ArgumentError, "The spec's name is required when filtering by an expectation's name"
|
64
|
-
end
|
65
|
-
|
66
|
-
if spec_name && file_name.blank?
|
67
|
-
raise ArgumentError, "The spec's filename is required when filtering by a spec's name"
|
68
|
-
end
|
69
|
-
|
70
|
-
specs.select! { |spec| spec.file_name == file_name } if file_name
|
71
|
-
specs.select! { |spec| spec.name == spec_name } if spec_name
|
72
|
-
|
73
|
-
if expectation_name
|
74
|
-
specs.each do |spec|
|
75
|
-
spec.expectations.select! { |expectation| expectation.name == expectation_name }
|
76
|
-
end
|
77
|
-
end
|
37
|
+
#
|
38
|
+
# Absolute path to the file containing this spec
|
39
|
+
#
|
40
|
+
# @return [String] The file path
|
41
|
+
#
|
42
|
+
attr_reader :file_path
|
78
43
|
|
79
|
-
|
80
|
-
|
44
|
+
#
|
45
|
+
# Base name of the file without path or extension
|
46
|
+
#
|
47
|
+
# @return [String] The file name
|
48
|
+
#
|
49
|
+
attr_reader :file_name
|
81
50
|
|
82
|
-
|
51
|
+
#
|
52
|
+
# Whether to enable debugging for this spec
|
53
|
+
#
|
54
|
+
# @return [Boolean] Debug flag
|
55
|
+
#
|
56
|
+
attr_reader :debug
|
83
57
|
|
84
|
-
|
58
|
+
#
|
59
|
+
# Line number in the source file where this spec is defined
|
60
|
+
#
|
61
|
+
# @return [Integer] The line number
|
62
|
+
#
|
63
|
+
attr_reader :line_number
|
85
64
|
|
86
|
-
|
65
|
+
#
|
66
|
+
# The expectations to test for this spec
|
67
|
+
#
|
68
|
+
# @return [Array<Expectation>] The expectations
|
69
|
+
#
|
70
|
+
attr_accessor :expectations
|
87
71
|
|
88
72
|
#
|
89
|
-
# Creates a
|
73
|
+
# Creates a new spec instance
|
74
|
+
#
|
75
|
+
# @param id [String] Unique identifier
|
76
|
+
# @param name [String] Human-readable name
|
77
|
+
# @param file_path [String] Absolute path to source file
|
78
|
+
# @param file_name [String] Base name of file
|
79
|
+
# @param debug [Boolean] Whether to enable debugging
|
80
|
+
# @param line_number [Integer] Line number in source
|
81
|
+
# @param expectations [Array<Hash>] Expectation configurations
|
90
82
|
#
|
91
|
-
# @
|
92
|
-
# @param file_path [String] The path where this spec is defined
|
93
|
-
# @param **input [Hash] Any attributes related to the spec, including expectations
|
94
|
-
# See Normalizer::Spec
|
83
|
+
# @return [Spec] A new spec instance
|
95
84
|
#
|
96
|
-
def initialize(name:, file_path:, file_name:, line_number:,
|
85
|
+
def initialize(id:, name:, file_path:, file_name:, debug:, line_number:, expectations:)
|
86
|
+
@id = id
|
97
87
|
@name = name
|
98
88
|
@file_path = file_path
|
99
89
|
@file_name = file_name
|
90
|
+
@debug = debug
|
100
91
|
@line_number = line_number
|
101
|
-
|
102
|
-
input = Normalizer.normalize_spec!(input)
|
103
|
-
|
104
|
-
# Don't pass this down to the expectations
|
105
|
-
@debug = input.delete(:debug) || false
|
106
|
-
|
107
|
-
global_options = normalize_global_options(input)
|
108
|
-
|
109
|
-
@expectations =
|
110
|
-
input[:expectations].map.with_index do |expectation_input, index|
|
111
|
-
Expectation.new(expectation_input, global_options:)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def define
|
116
|
-
Runner.define_spec(self)
|
92
|
+
@expectations = expectations.map { |e| Expectation.new(**e) }
|
117
93
|
end
|
118
94
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
95
|
+
#
|
96
|
+
# Converts the spec to a hash representation
|
97
|
+
#
|
98
|
+
# @return [Hash] Hash representation
|
99
|
+
#
|
100
|
+
def to_h
|
101
|
+
{
|
102
|
+
name:,
|
103
|
+
file_path:,
|
104
|
+
file_name:,
|
105
|
+
debug:,
|
106
|
+
line_number:,
|
107
|
+
expectations: expectations.map(&:to_h)
|
108
|
+
}
|
124
109
|
end
|
125
110
|
end
|
126
111
|
end
|
112
|
+
|
113
|
+
require_relative "spec/expectation"
|
data/lib/spec_forge/type.rb
CHANGED
@@ -1,24 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SpecForge
|
4
|
+
#
|
5
|
+
# Provides helper methods for checking types
|
6
|
+
# Useful for working with both regular objects and Attribute delegators
|
7
|
+
#
|
4
8
|
module Type
|
5
9
|
#
|
6
|
-
# Checks if the object is a Hash
|
10
|
+
# Checks if the object is a Hash or a ResolvableHash delegator
|
7
11
|
#
|
8
12
|
# @param object [Object] The object to check
|
9
13
|
#
|
10
|
-
# @return [Boolean]
|
14
|
+
# @return [Boolean] True if the object is a hash-like structure
|
11
15
|
#
|
12
16
|
def self.hash?(object)
|
13
17
|
object.is_a?(Hash) || object.is_a?(Attribute::ResolvableHash)
|
14
18
|
end
|
15
19
|
|
16
20
|
#
|
17
|
-
# Checks if the object is an Array
|
21
|
+
# Checks if the object is an Array or a ResolvableArray delegator
|
18
22
|
#
|
19
23
|
# @param object [Object] The object to check
|
20
24
|
#
|
21
|
-
# @return [Boolean]
|
25
|
+
# @return [Boolean] True if the object is an array-like structure
|
22
26
|
#
|
23
27
|
def self.array?(object)
|
24
28
|
object.is_a?(Array) || object.is_a?(Attribute::ResolvableArray)
|
@@ -28,8 +32,22 @@ end
|
|
28
32
|
|
29
33
|
#
|
30
34
|
# Represents Hash/ResolvableHash in a form that can be used in a case statement
|
35
|
+
# Allows for type switching on hash-like objects
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# case value
|
39
|
+
# when HashLike
|
40
|
+
# # Handle hash-like objects
|
41
|
+
# end
|
31
42
|
#
|
32
43
|
class HashLike
|
44
|
+
#
|
45
|
+
# Provides custom type matching for use in case statements
|
46
|
+
#
|
47
|
+
# @param object [Object] The object to check against the type
|
48
|
+
#
|
49
|
+
# @return [Boolean] Whether the object matches the type
|
50
|
+
#
|
33
51
|
def self.===(object)
|
34
52
|
SpecForge::Type.hash?(object)
|
35
53
|
end
|
@@ -37,8 +55,22 @@ end
|
|
37
55
|
|
38
56
|
#
|
39
57
|
# Represents Array/ResolvableArray in a form that can be used in a case statement
|
58
|
+
# Allows for type switching on array-like objects
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# case value
|
62
|
+
# when ArrayLike
|
63
|
+
# # Handle array-like objects
|
64
|
+
# end
|
40
65
|
#
|
41
66
|
class ArrayLike
|
67
|
+
#
|
68
|
+
# Provides custom type matching for use in case statements
|
69
|
+
#
|
70
|
+
# @param object [Object] The object to check against the type
|
71
|
+
#
|
72
|
+
# @return [Boolean] Whether the object matches the type
|
73
|
+
#
|
42
74
|
def self.===(object)
|
43
75
|
SpecForge::Type.array?(object)
|
44
76
|
end
|