sus 0.34.0 → 0.35.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/context/getting-started.md +352 -0
  4. data/context/index.yaml +9 -0
  5. data/context/mocking.md +100 -30
  6. data/context/{shared.md → shared-contexts.md} +29 -2
  7. data/lib/sus/assertions.rb +91 -18
  8. data/lib/sus/base.rb +13 -1
  9. data/lib/sus/be.rb +84 -0
  10. data/lib/sus/be_truthy.rb +16 -0
  11. data/lib/sus/be_within.rb +25 -0
  12. data/lib/sus/clock.rb +21 -0
  13. data/lib/sus/config.rb +58 -1
  14. data/lib/sus/context.rb +28 -5
  15. data/lib/sus/describe.rb +14 -0
  16. data/lib/sus/expect.rb +23 -0
  17. data/lib/sus/file.rb +38 -0
  18. data/lib/sus/filter.rb +21 -0
  19. data/lib/sus/fixtures/temporary_directory_context.rb +27 -0
  20. data/lib/sus/fixtures.rb +1 -0
  21. data/lib/sus/have/all.rb +8 -0
  22. data/lib/sus/have/any.rb +8 -0
  23. data/lib/sus/have.rb +42 -0
  24. data/lib/sus/have_duration.rb +16 -0
  25. data/lib/sus/identity.rb +44 -1
  26. data/lib/sus/integrations.rb +1 -0
  27. data/lib/sus/it.rb +33 -0
  28. data/lib/sus/it_behaves_like.rb +16 -0
  29. data/lib/sus/let.rb +3 -0
  30. data/lib/sus/mock.rb +39 -1
  31. data/lib/sus/output/backtrace.rb +31 -1
  32. data/lib/sus/output/bar.rb +17 -0
  33. data/lib/sus/output/buffered.rb +32 -1
  34. data/lib/sus/output/lines.rb +10 -0
  35. data/lib/sus/output/messages.rb +26 -3
  36. data/lib/sus/output/null.rb +16 -2
  37. data/lib/sus/output/progress.rb +29 -1
  38. data/lib/sus/output/status.rb +13 -0
  39. data/lib/sus/output/structured.rb +14 -1
  40. data/lib/sus/output/text.rb +33 -1
  41. data/lib/sus/output/xterm.rb +11 -1
  42. data/lib/sus/output.rb +9 -0
  43. data/lib/sus/raise_exception.rb +16 -0
  44. data/lib/sus/receive.rb +82 -0
  45. data/lib/sus/registry.rb +20 -1
  46. data/lib/sus/respond_to.rb +29 -2
  47. data/lib/sus/shared.rb +16 -0
  48. data/lib/sus/tree.rb +10 -0
  49. data/lib/sus/version.rb +2 -1
  50. data/lib/sus/with.rb +18 -0
  51. data/readme.md +8 -0
  52. data/releases.md +4 -0
  53. data.tar.gz.sig +0 -0
  54. metadata +3 -3
  55. metadata.gz.sig +0 -0
  56. data/context/usage.md +0 -380
data/lib/sus/filter.rb CHANGED
@@ -12,13 +12,18 @@ module Sus
12
12
  #
13
13
  # When the filter is used to enumerate the registry, it will only return the tests that match the suffix.
14
14
  class Filter
15
+ # Represents an index of contexts by their identity keys.
15
16
  class Index
17
+ # Initialize a new Index.
16
18
  def initialize
17
19
  @contexts = {}
18
20
  end
19
21
 
22
+ # @attribute [Hash] A hash mapping identity keys to contexts.
20
23
  attr :contexts
21
24
 
25
+ # Add all children from a parent context to the index.
26
+ # @parameter parent [Object] The parent context.
22
27
  def add(parent)
23
28
  parent.children&.each do |identity, child|
24
29
  insert(identity, child)
@@ -26,6 +31,10 @@ module Sus
26
31
  end
27
32
  end
28
33
 
34
+ # Insert a context into the index.
35
+ # @parameter identity [Identity] The identity of the context.
36
+ # @parameter context [Object] The context to index.
37
+ # @raises [KeyError] If a context with the same key already exists.
29
38
  def insert(identity, context)
30
39
  key = identity.key
31
40
 
@@ -36,17 +45,24 @@ module Sus
36
45
  end
37
46
  end
38
47
 
48
+ # Look up a context by its key.
49
+ # @parameter key [String] The identity key.
50
+ # @returns [Object, nil] The context if found.
39
51
  def [] key
40
52
  @contexts[key]
41
53
  end
42
54
  end
43
55
 
56
+ # Initialize a new Filter.
57
+ # @parameter registry [Registry] The registry to filter.
44
58
  def initialize(registry = Registry.new)
45
59
  @registry = registry
46
60
  @index = nil
47
61
  @keys = Array.new
48
62
  end
49
63
 
64
+ # Load a target path, optionally with a filter suffix.
65
+ # @parameter target [String] The target path, optionally with a ":suffix" filter.
50
66
  def load(target)
51
67
  path, filter = target.split(":", 2)
52
68
 
@@ -57,6 +73,8 @@ module Sus
57
73
  end
58
74
  end
59
75
 
76
+ # Iterate over filtered test cases.
77
+ # @yields {|test| ...} Each test case that matches the filter.
60
78
  def each(&block)
61
79
  if @keys.any?
62
80
  @index = Index.new
@@ -72,6 +90,9 @@ module Sus
72
90
  end
73
91
  end
74
92
 
93
+ # Execute filtered tests.
94
+ # @parameter assertions [Assertions] Optional assertions instance to use.
95
+ # @returns [Assertions] The assertions instance with results.
75
96
  def call(assertions = Assertions.default)
76
97
  if @keys.any?
77
98
  @index = Index.new
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require "tmpdir"
7
+
8
+ module Sus
9
+ module Fixtures
10
+ # Provides a temporary directory context for tests that need isolated file system access.
11
+ module TemporaryDirectoryContext
12
+ # Set up a temporary directory before the test and clean it up after.
13
+ # @yields {|&block| ...} The test block to execute.
14
+ def around(&block)
15
+ Dir.mktmpdir do |root|
16
+ @root = root
17
+ super(&block)
18
+ @root = nil
19
+ end
20
+ end
21
+
22
+ # @attribute [String] The path to the temporary directory root.
23
+ attr :root
24
+ end
25
+ end
26
+ end
27
+
data/lib/sus/fixtures.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2022, by Samuel Williams.
5
5
 
6
6
  module Sus
7
+ # @namespace
7
8
  module Fixtures
8
9
  end
9
10
  end
data/lib/sus/have/all.rb CHANGED
@@ -5,11 +5,16 @@
5
5
 
6
6
  module Sus
7
7
  module Have
8
+ # Represents a predicate that checks if the subject matches all of the given predicates.
8
9
  class All
10
+ # Initialize a new All predicate.
11
+ # @parameter predicates [Array] The predicates to check.
9
12
  def initialize(predicates)
10
13
  @predicates = predicates
11
14
  end
12
15
 
16
+ # Print a representation of this predicate.
17
+ # @parameter output [Output] The output target.
13
18
  def print(output)
14
19
  first = true
15
20
  output.write("have {")
@@ -25,6 +30,9 @@ module Sus
25
30
  output.write("}")
26
31
  end
27
32
 
33
+ # Evaluate this predicate against a subject.
34
+ # @parameter assertions [Assertions] The assertions instance to use.
35
+ # @parameter subject [Object] The subject to evaluate.
28
36
  def call(assertions, subject)
29
37
  assertions.nested(self) do |assertions|
30
38
  @predicates.each do |predicate|
data/lib/sus/have/any.rb CHANGED
@@ -5,11 +5,16 @@
5
5
 
6
6
  module Sus
7
7
  module Have
8
+ # Represents a predicate that checks if the subject matches any of the given predicates.
8
9
  class Any
10
+ # Initialize a new Any predicate.
11
+ # @parameter predicates [Array] The predicates to check.
9
12
  def initialize(predicates)
10
13
  @predicates = predicates
11
14
  end
12
15
 
16
+ # Print a representation of this predicate.
17
+ # @parameter output [Output] The output target.
13
18
  def print(output)
14
19
  first = true
15
20
  output.write("have any {")
@@ -25,6 +30,9 @@ module Sus
25
30
  output.write("}")
26
31
  end
27
32
 
33
+ # Evaluate this predicate against a subject.
34
+ # @parameter assertions [Assertions] The assertions instance to use.
35
+ # @parameter subject [Object] The subject to evaluate.
28
36
  def call(assertions, subject)
29
37
  assertions.nested(self) do |assertions|
30
38
  @predicates.each do |predicate|
data/lib/sus/have.rb CHANGED
@@ -7,17 +7,27 @@ require_relative "have/all"
7
7
  require_relative "have/any"
8
8
 
9
9
  module Sus
10
+ # Represents predicates for checking collections and object attributes.
10
11
  module Have
12
+ # Represents a predicate that checks if a hash has a specific key.
11
13
  class Key
14
+ # Initialize a new Key predicate.
15
+ # @parameter name [Object] The key name to check for.
16
+ # @parameter predicate [Object, nil] Optional predicate to apply to the key's value.
12
17
  def initialize(name, predicate = nil)
13
18
  @name = name
14
19
  @predicate = predicate
15
20
  end
16
21
 
22
+ # Print a representation of this predicate.
23
+ # @parameter output [Output] The output target.
17
24
  def print(output)
18
25
  output.write("key ", :variable, @name.inspect, :reset, " ", @predicate, :reset)
19
26
  end
20
27
 
28
+ # Evaluate this predicate against a subject.
29
+ # @parameter assertions [Assertions] The assertions instance to use.
30
+ # @parameter subject [Object] The subject to evaluate (should be a hash).
21
31
  def call(assertions, subject)
22
32
  # We want to group all the assertions in to a distinct group:
23
33
  assertions.nested(self, distinct: true) do |assertions|
@@ -29,16 +39,25 @@ module Sus
29
39
  end
30
40
  end
31
41
 
42
+ # Represents a predicate that checks if an object has a specific attribute.
32
43
  class Attribute
44
+ # Initialize a new Attribute predicate.
45
+ # @parameter name [Symbol, String] The attribute name to check for.
46
+ # @parameter predicate [Object] The predicate to apply to the attribute's value.
33
47
  def initialize(name, predicate)
34
48
  @name = name
35
49
  @predicate = predicate
36
50
  end
37
51
 
52
+ # Print a representation of this predicate.
53
+ # @parameter output [Output] The output target.
38
54
  def print(output)
39
55
  output.write("attribute ", :variable, @name.to_s, :reset, " ", @predicate, :reset)
40
56
  end
41
57
 
58
+ # Evaluate this predicate against a subject.
59
+ # @parameter assertions [Assertions] The assertions instance to use.
60
+ # @parameter subject [Object] The subject to evaluate.
42
61
  def call(assertions, subject)
43
62
  assertions.nested(self, distinct: true) do |assertions|
44
63
  assertions.assert(subject.respond_to?(@name), "has attribute")
@@ -49,15 +68,23 @@ module Sus
49
68
  end
50
69
  end
51
70
 
71
+ # Represents a predicate that checks if a collection has a value matching a predicate.
52
72
  class Value
73
+ # Initialize a new Value predicate.
74
+ # @parameter predicate [Object, nil] The predicate to apply to each value in the collection.
53
75
  def initialize(predicate)
54
76
  @predicate = predicate
55
77
  end
56
78
 
79
+ # Print a representation of this predicate.
80
+ # @parameter output [Output] The output target.
57
81
  def print(output)
58
82
  output.write("value ", @predicate, :reset)
59
83
  end
60
84
 
85
+ # Evaluate this predicate against a subject.
86
+ # @parameter assertions [Assertions] The assertions instance to use.
87
+ # @parameter subject [Object] The subject to evaluate (should be enumerable).
61
88
  def call(assertions, subject)
62
89
  index = 0
63
90
 
@@ -73,10 +100,16 @@ module Sus
73
100
  end
74
101
 
75
102
  class Base
103
+ # Create a predicate that checks if the subject has all of the given predicates.
104
+ # @parameter predicates [Array] The predicates to check.
105
+ # @returns [Have::All] A Have::All predicate.
76
106
  def have(*predicates)
77
107
  Have::All.new(predicates)
78
108
  end
79
109
 
110
+ # Create a predicate that checks if the subject (hash) has the specified keys.
111
+ # @parameter keys [Array] Keys to check for. Can be symbols/strings or hashes with key-predicate pairs.
112
+ # @returns [Have::All] A Have::All predicate.
80
113
  def have_keys(*keys)
81
114
  predicates = []
82
115
 
@@ -93,6 +126,9 @@ module Sus
93
126
  Have::All.new(predicates)
94
127
  end
95
128
 
129
+ # Create a predicate that checks if the subject has the specified attributes with matching values.
130
+ # @parameter attributes [Hash] A hash of attribute names to predicates.
131
+ # @returns [Have::All] A Have::All predicate.
96
132
  def have_attributes(**attributes)
97
133
  predicates = attributes.map do |key, value|
98
134
  Have::Attribute.new(key, value)
@@ -101,10 +137,16 @@ module Sus
101
137
  Have::All.new(predicates)
102
138
  end
103
139
 
140
+ # Create a predicate that checks if the subject matches any of the given predicates.
141
+ # @parameter predicates [Array] The predicates to check.
142
+ # @returns [Have::Any] A Have::Any predicate.
104
143
  def have_any(*predicates)
105
144
  Have::Any.new(predicates)
106
145
  end
107
146
 
147
+ # Create a predicate that checks if the subject (collection) has any value matching the predicate.
148
+ # @parameter predicate [Object] The predicate to apply to each value.
149
+ # @returns [Have::Any] A Have::Any predicate.
108
150
  def have_value(predicate)
109
151
  Have::Any.new([Have::Value.new(predicate)])
110
152
  end
@@ -4,16 +4,24 @@
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  module Sus
7
+ # Represents a predicate that measures the duration of a block execution.
7
8
  class HaveDuration
9
+ # Initialize a new HaveDuration predicate.
10
+ # @parameter predicate [Object] The predicate to apply to the measured duration.
8
11
  def initialize(predicate)
9
12
  @predicate = predicate
10
13
  end
11
14
 
15
+ # Print a representation of this predicate.
16
+ # @parameter output [Output] The output target.
12
17
  def print(output)
13
18
  output.write("have duration ")
14
19
  @predicate.print(output)
15
20
  end
16
21
 
22
+ # Evaluate this predicate against a subject (block).
23
+ # @parameter assertions [Assertions] The assertions instance to use.
24
+ # @parameter subject [Proc] The block to measure.
17
25
  def call(assertions, subject)
18
26
  assertions.nested(self) do |assertions|
19
27
  Expect.new(assertions, measure(subject)).to(@predicate)
@@ -22,6 +30,9 @@ module Sus
22
30
 
23
31
  private
24
32
 
33
+ # Measure the duration of executing a block.
34
+ # @parameter subject [Proc] The block to measure.
35
+ # @returns [Float] The duration in seconds.
25
36
  def measure(subject)
26
37
  start_time = now
27
38
 
@@ -30,12 +41,17 @@ module Sus
30
41
  return now - start_time
31
42
  end
32
43
 
44
+ # Get the current monotonic time.
45
+ # @returns [Float] The current time in seconds.
33
46
  def now
34
47
  ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
35
48
  end
36
49
  end
37
50
 
38
51
  class Base
52
+ # Create a predicate that measures the duration of a block execution.
53
+ # @parameter predicate [Object] The predicate to apply to the measured duration.
54
+ # @returns [HaveDuration] A new HaveDuration predicate.
39
55
  def have_duration(...)
40
56
  HaveDuration.new(...)
41
57
  end
data/lib/sus/identity.rb CHANGED
@@ -4,22 +4,42 @@
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  module Sus
7
+ # Represents a unique identity for a test or context, used for identification and location tracking.
7
8
  class Identity
9
+ # Create an identity for a file.
10
+ # @parameter parent [Identity, nil] The parent identity.
11
+ # @parameter path [String] The file path.
12
+ # @parameter name [String] The name (defaults to path).
13
+ # @parameter options [Hash] Additional options.
14
+ # @returns [Identity] A new Identity instance.
8
15
  def self.file(parent, path, name = path, **options)
9
16
  self.new(path, name, nil, nil, **options)
10
17
  end
11
18
 
19
+ # Create a nested identity.
20
+ # @parameter parent [Identity, nil] The parent identity.
21
+ # @parameter name [String] The name of this identity.
22
+ # @parameter location [Thread::Backtrace::Location, nil] Optional location (defaults to caller location).
23
+ # @parameter options [Hash] Additional options.
24
+ # @returns [Identity] A new Identity instance.
12
25
  def self.nested(parent, name, location = nil, **options)
13
26
  location ||= caller_locations(3...4).first
14
27
 
15
28
  self.new(location.path, name, location.lineno, parent, **options)
16
29
  end
17
30
 
31
+ # Create an identity for the current location.
32
+ # @returns [Identity] A new Identity instance for the current caller location.
18
33
  def self.current
19
34
  self.nested(nil, nil, caller_locations(1...2).first)
20
35
  end
21
36
 
22
- # @parameter unique [Boolean | Symbol] Whether this identity is unique or needs a unique key/line number suffix.
37
+ # Initialize a new Identity.
38
+ # @parameter path [String] The file path.
39
+ # @parameter name [String, nil] Optional name.
40
+ # @parameter line [Integer, nil] Optional line number.
41
+ # @parameter parent [Identity, nil] Optional parent identity.
42
+ # @parameter unique [Boolean, Symbol] Whether this identity is unique or needs a unique key/line number suffix.
23
43
  def initialize(path, name = nil, line = nil, parent = nil, unique: true)
24
44
  @path = path
25
45
  @name = name
@@ -30,20 +50,34 @@ module Sus
30
50
  @key = nil
31
51
  end
32
52
 
53
+ # Create a new identity with a different line number.
54
+ # @parameter line [Integer] The line number.
55
+ # @returns [Identity] A new Identity instance.
33
56
  def with_line(line)
34
57
  self.class.new(@path, @name, line, @parent, unique: @unique)
35
58
  end
36
59
 
60
+ # @attribute [String] The file path.
37
61
  attr :path
62
+
63
+ # @attribute [String, nil] The name.
38
64
  attr :name
65
+
66
+ # @attribute [Integer, nil] The line number.
39
67
  attr :line
68
+
69
+ # @attribute [Identity, nil] The parent identity.
40
70
  attr :parent
71
+
72
+ # @attribute [Boolean, Symbol] Whether this identity is unique.
41
73
  attr :unique
42
74
 
75
+ # @returns [String] A string representation of this identity (the key).
43
76
  def to_s
44
77
  self.key
45
78
  end
46
79
 
80
+ # @returns [Hash] A hash containing the path and line number.
47
81
  def to_location
48
82
  {
49
83
  path: ::File.expand_path(@path),
@@ -51,10 +85,14 @@ module Sus
51
85
  }
52
86
  end
53
87
 
88
+ # @returns [String] An inspect representation of this identity.
54
89
  def inspect
55
90
  "\#<#{self.class} #{self.to_s}>"
56
91
  end
57
92
 
93
+ # Check if this identity matches another.
94
+ # @parameter other [Identity] The identity to match against.
95
+ # @returns [Boolean] Whether the identities match.
58
96
  def match?(other)
59
97
  if path = other.path
60
98
  return false unless path === @path
@@ -69,12 +107,15 @@ module Sus
69
107
  end
70
108
  end
71
109
 
110
+ # Iterate over this identity and all its parents.
111
+ # @yields {|identity| ...} Each identity in the chain.
72
112
  def each(&block)
73
113
  @parent&.each(&block)
74
114
 
75
115
  yield self
76
116
  end
77
117
 
118
+ # @returns [String] A unique key for this identity.
78
119
  def key
79
120
  unless @key
80
121
  key = Array.new
@@ -89,6 +130,8 @@ module Sus
89
130
  end
90
131
 
91
132
  # Given a set of locations, find the first one which matches this identity and return a new identity with the updated line number. This can be used to extract a location from a backtrace.
133
+ # @parameter locations [Array(Thread::Backtrace::Location), nil] Optional locations to search (defaults to caller locations).
134
+ # @returns [Identity] A new identity with updated line number if a match is found, otherwise returns self.
92
135
  def scoped(locations = nil)
93
136
  if locations
94
137
  # This code path is normally taken if we've got an exception with a backtrace:
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
6
  module Sus
7
+ # @namespace
7
8
  module Integrations
8
9
  end
9
10
  end
data/lib/sus/it.rb CHANGED
@@ -6,7 +6,14 @@
6
6
  require_relative "context"
7
7
 
8
8
  module Sus
9
+ # Represents an individual test case.
9
10
  module It
11
+ # Build a new test case class.
12
+ # @parameter parent [Class] The parent context class.
13
+ # @parameter description [String | Nil] Optional description of the test.
14
+ # @parameter unique [Boolean] Whether the identity should be unique.
15
+ # @yields {...} Optional block containing the test code.
16
+ # @returns [Class] A new test case class.
10
17
  def self.build(parent, description = nil, unique: true, &block)
11
18
  base = Class.new(parent)
12
19
  base.extend(It)
@@ -21,20 +28,26 @@ module Sus
21
28
  return base
22
29
  end
23
30
 
31
+ # @returns [Boolean] Always returns true, as test cases are leaf nodes.
24
32
  def leaf?
25
33
  true
26
34
  end
27
35
 
36
+ # Print a representation of this test case.
37
+ # @parameter output [Output] The output target.
28
38
  def print(output)
29
39
  self.superclass.print(output)
30
40
 
31
41
  output.write(" it ", :it, self.description, :reset, " ", :identity, self.identity.to_s, :reset)
32
42
  end
33
43
 
44
+ # @returns [String] A string representation of this test case.
34
45
  def to_s
35
46
  "it #{description}"
36
47
  end
37
48
 
49
+ # Execute this test case.
50
+ # @parameter assertions [Assertions] The assertions instance to use.
38
51
  def call(assertions)
39
52
  assertions.nested(self, identity: self.identity, isolated: true, measure: true) do |assertions|
40
53
  instance = self.new(assertions)
@@ -45,6 +58,10 @@ module Sus
45
58
  end
46
59
  end
47
60
 
61
+ # Handle skip logic for the test case.
62
+ # @parameter instance [Base] The test instance.
63
+ # @parameter assertions [Assertions] The assertions instance.
64
+ # @returns [Object] The result of calling the test instance.
48
65
  def handle_skip(instance, assertions)
49
66
  catch(:skip) do
50
67
  return instance.call
@@ -53,6 +70,10 @@ module Sus
53
70
  end
54
71
 
55
72
  module Context
73
+ # Define a new test case.
74
+ # @parameter description [String] The description of the test.
75
+ # @parameter options [Hash] Additional options.
76
+ # @yields {...} The test code.
56
77
  def it(...)
57
78
  add It.build(self, ...)
58
79
  end
@@ -66,30 +87,42 @@ module Sus
66
87
  throw :skip, reason
67
88
  end
68
89
 
90
+ # Skip the test unless a method is defined on the target.
91
+ # @parameter method [Symbol] The method name to check.
92
+ # @parameter target [Module, Class] The target class or module to check.
69
93
  def skip_unless_method_defined(method, target)
70
94
  unless target.method_defined?(method)
71
95
  skip "Method #{method} is not defined in #{target}!"
72
96
  end
73
97
  end
74
98
 
99
+ # Skip the test unless a constant is defined.
100
+ # @parameter constant [Symbol, String] The constant name to check.
101
+ # @parameter target [Module, Class] The target class or module to check.
75
102
  def skip_unless_constant_defined(constant, target = Object)
76
103
  unless target.const_defined?(constant)
77
104
  skip "Constant #{constant} is not defined in #{target}!"
78
105
  end
79
106
  end
80
107
 
108
+ # Skip the test unless the Ruby version meets the minimum requirement.
109
+ # @parameter version [String] The minimum Ruby version required.
81
110
  def skip_unless_minimum_ruby_version(version)
82
111
  unless RUBY_VERSION >= version
83
112
  skip "Ruby #{version} is required, but running #{RUBY_VERSION}!"
84
113
  end
85
114
  end
86
115
 
116
+ # Skip the test if the Ruby version exceeds the maximum supported version.
117
+ # @parameter version [String] The maximum Ruby version supported.
87
118
  def skip_if_maximum_ruby_version(version)
88
119
  if RUBY_VERSION >= version
89
120
  skip "Ruby #{version} is not supported, but running #{RUBY_VERSION}!"
90
121
  end
91
122
  end
92
123
 
124
+ # Skip the test if the Ruby platform matches the pattern.
125
+ # @parameter pattern [Regexp] The platform pattern to match against.
93
126
  def skip_if_ruby_platform(pattern)
94
127
  if match = RUBY_PLATFORM.match(pattern)
95
128
  skip "Ruby platform #{match} is not supported!"
@@ -6,11 +6,20 @@
6
6
  require_relative "context"
7
7
 
8
8
  module Sus
9
+ # Represents a test context that behaves like a shared context.
9
10
  module ItBehavesLike
10
11
  extend Context
11
12
 
13
+ # @attribute [Shared] The shared context being used.
12
14
  attr_accessor :shared
13
15
 
16
+ # Build a new ItBehavesLike context.
17
+ # @parameter parent [Class] The parent context class.
18
+ # @parameter shared [Shared] The shared context to use.
19
+ # @parameter arguments [Array | Nil] Optional arguments to pass to the shared context.
20
+ # @parameter unique [Boolean] Whether the identity should be unique.
21
+ # @yields {...} Optional block to execute before the shared context.
22
+ # @returns [Class] A new test class that behaves like the shared context.
14
23
  def self.build(parent, shared, arguments = nil, unique: false, &block)
15
24
  base = Class.new(parent)
16
25
  base.singleton_class.prepend(ItBehavesLike)
@@ -28,6 +37,8 @@ module Sus
28
37
  return base
29
38
  end
30
39
 
40
+ # Print a representation of this context.
41
+ # @parameter output [Output] The output target.
31
42
  def print(output)
32
43
  self.superclass.print(output)
33
44
  output.write(" it behaves like ", :describe, self.description, :reset)
@@ -35,6 +46,11 @@ module Sus
35
46
  end
36
47
 
37
48
  module Context
49
+ # Define a test context that behaves like a shared context.
50
+ # @parameter shared [Shared] The shared context to use.
51
+ # @parameter arguments [Array] Optional arguments to pass to the shared context.
52
+ # @parameter options [Hash] Additional options.
53
+ # @yields {...} Optional block to execute before the shared context.
38
54
  def it_behaves_like(shared, *arguments, **options, &block)
39
55
  add ItBehavesLike.build(self, shared, arguments, **options, &block)
40
56
  end
data/lib/sus/let.rb CHANGED
@@ -7,6 +7,9 @@ require_relative "context"
7
7
 
8
8
  module Sus
9
9
  module Context
10
+ # Define a lazy variable that is evaluated when first accessed.
11
+ # @parameter name [Symbol] The name of the variable.
12
+ # @yields {...} The block that computes the variable value.
10
13
  def let(name, &block)
11
14
  instance_variable = :"@#{name}"
12
15