sus 0.33.1 → 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 (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/context/{usage.md → getting-started.md} +58 -37
  4. data/context/index.yaml +22 -0
  5. data/context/mocking.md +100 -30
  6. data/context/{shared.md → shared-contexts.md} +29 -2
  7. data/lib/sus/assertions.rb +93 -20
  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 +59 -2
  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 +53 -15
  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 +28 -5
  36. data/lib/sus/output/null.rb +18 -4
  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 +85 -3
  45. data/lib/sus/registry.rb +20 -1
  46. data/lib/sus/respond_to.rb +30 -3
  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 +12 -0
  52. data/releases.md +8 -0
  53. data.tar.gz.sig +0 -0
  54. metadata +6 -4
  55. metadata.gz.sig +0 -0
@@ -9,11 +9,18 @@ require_relative "lines"
9
9
 
10
10
  module Sus
11
11
  module Output
12
+ # Represents a progress tracker for test execution.
12
13
  class Progress
14
+ # Get the current monotonic time.
15
+ # @returns [Float] The current time in seconds.
13
16
  def self.now
14
17
  ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
15
18
  end
16
19
 
20
+ # Initialize a new Progress tracker.
21
+ # @parameter output [Output] The output handler.
22
+ # @parameter total [Integer] The total number of items to track.
23
+ # @parameter minimum_output_duration [Float] Minimum duration before showing output (unused).
17
24
  def initialize(output, total = 0, minimum_output_duration: 1.0)
18
25
  @output = output
19
26
  @subject = subject
@@ -30,35 +37,47 @@ module Sus
30
37
  @total = total
31
38
  end
32
39
 
40
+ # @attribute [Object, nil] The subject being tracked.
33
41
  attr :subject
42
+
43
+ # @attribute [Integer] The current progress value.
34
44
  attr :current
45
+
46
+ # @attribute [Integer] The total value.
35
47
  attr :total
36
48
 
49
+ # @returns [Float] The elapsed duration in seconds.
37
50
  def duration
38
51
  Progress.now - @start_time
39
52
  end
40
53
 
54
+ # @returns [Float] The progress as a fraction (0.0 to 1.0).
41
55
  def progress
42
56
  @current.to_f / @total.to_f
43
57
  end
44
58
 
59
+ # @returns [Integer] The remaining items to process.
45
60
  def remaining
46
61
  @total - @current
47
62
  end
48
63
 
64
+ # @returns [Float, nil] The average duration per item, or nil if no items completed.
49
65
  def average_duration
50
66
  if @current > 0
51
67
  duration / @current
52
68
  end
53
69
  end
54
70
 
71
+ # @returns [Float, nil] The estimated remaining time, or nil if cannot be calculated.
55
72
  def estimated_remaining_time
56
73
  if average_duration = self.average_duration
57
74
  average_duration * remaining
58
75
  end
59
76
  end
60
77
 
61
- # Increase the amont of work done.
78
+ # Increase the amount of work done.
79
+ # @parameter amount [Integer] The amount to increment by.
80
+ # @returns [Progress] Returns self for method chaining.
62
81
  def increment(amount = 1)
63
82
  @current += amount
64
83
 
@@ -69,6 +88,8 @@ module Sus
69
88
  end
70
89
 
71
90
  # Increase the total size of the progress.
91
+ # @parameter amount [Integer] The amount to expand by.
92
+ # @returns [Progress] Returns self for method chaining.
72
93
  def expand(amount = 1)
73
94
  @total += amount
74
95
 
@@ -78,16 +99,23 @@ module Sus
78
99
  return self
79
100
  end
80
101
 
102
+ # Report the status of a specific item.
103
+ # @parameter index [Integer] The index of the item.
104
+ # @parameter context [Object] The context to display.
105
+ # @parameter state [Symbol] The state (:free or :busy).
106
+ # @returns [Progress] Returns self for method chaining.
81
107
  def report(index, context, state)
82
108
  @lines&.[]=(index+1, Status.new(state, context))
83
109
 
84
110
  return self
85
111
  end
86
112
 
113
+ # Clear the progress display.
87
114
  def clear
88
115
  @lines&.clear
89
116
  end
90
117
 
118
+ # @returns [String] A string representation of the progress.
91
119
  def to_s
92
120
  if estimated_remaining_time = self.estimated_remaining_time
93
121
  "#{@current}/#{@total} completed in #{formatted_duration(self.duration)}, #{formatted_duration(estimated_remaining_time)} remaining"
@@ -5,27 +5,38 @@
5
5
 
6
6
  module Sus
7
7
  module Output
8
+ # Represents a status indicator for test execution.
8
9
  class Status
10
+ # Register status styling with an output handler.
11
+ # @parameter output [Output] The output handler to register with.
9
12
  def self.register(output)
10
13
  output[:free] ||= output.style(:blue)
11
14
  output[:busy] ||= output.style(:orange)
12
15
  end
13
16
 
17
+ # Initialize a new Status indicator.
18
+ # @parameter state [Symbol] The state (:free or :busy).
19
+ # @parameter context [Object, nil] Optional context to display.
14
20
  def initialize(state = :free, context = nil)
15
21
  @state = state
16
22
  @context = context
17
23
  end
18
24
 
25
+ # Status indicators for different states.
19
26
  INDICATORS = {
20
27
  busy: ["◑", "◒", "◐", "◓"],
21
28
  free: ["◌"]
22
29
  }
23
30
 
31
+ # Update the status.
32
+ # @parameter state [Symbol] The new state.
33
+ # @parameter context [Object, nil] Optional new context.
24
34
  def update(state, context = nil)
25
35
  @state = state
26
36
  @context = context
27
37
  end
28
38
 
39
+ # @returns [String] The current indicator character (animated for busy state).
29
40
  def indicator
30
41
  if indicators = INDICATORS[@state]
31
42
  return indicators[(Time.now.to_f * 10) % indicators.size]
@@ -34,6 +45,8 @@ module Sus
34
45
  return " "
35
46
  end
36
47
 
48
+ # Print the status to the output.
49
+ # @parameter output [Output] The output handler.
37
50
  def print(output)
38
51
  output.write(
39
52
  @state, self.indicator, " "
@@ -6,22 +6,35 @@
6
6
  require_relative "null"
7
7
 
8
8
  module Sus
9
- # Styled output output.
10
9
  module Output
10
+ # Represents a structured JSON output handler for machine-readable output.
11
11
  class Structured < Null
12
+ # Create a buffered structured output handler.
13
+ # @parameter io [IO] The IO object to write to.
14
+ # @parameter identity [Identity, nil] Optional identity.
15
+ # @returns [Buffered] A new Buffered instance wrapping a Structured handler.
12
16
  def self.buffered(...)
13
17
  Buffered.new(self.new(...))
14
18
  end
15
19
 
20
+ # Initialize a new Structured output handler.
21
+ # @parameter io [IO] The IO object to write to.
22
+ # @parameter identity [Identity, nil] Optional identity.
16
23
  def initialize(io, identity = nil)
17
24
  @io = io
18
25
  @identity = identity
19
26
  end
20
27
 
28
+ # Output a skip message as JSON.
29
+ # @parameter reason [String] The reason for skipping.
30
+ # @parameter identity [Identity, nil] The identity where the skip occurred.
21
31
  def skip(reason, identity)
22
32
  inform(reason.to_s, identity)
23
33
  end
24
34
 
35
+ # Output an informational message as JSON.
36
+ # @parameter message [String, Object] The message to output.
37
+ # @parameter identity [Identity, nil] The identity where the message was generated.
25
38
  def inform(message, identity)
26
39
  unless message.is_a?(String)
27
40
  message = message.inspect
@@ -8,9 +8,12 @@ require_relative "buffered"
8
8
 
9
9
  module Sus
10
10
  module Output
11
+ # Represents a plain text output handler without color support.
11
12
  class Text
12
13
  include Messages
13
14
 
15
+ # Initialize a new Text output handler.
16
+ # @parameter io [IO] The IO object to write to.
14
17
  def initialize(io)
15
18
  @io = io
16
19
 
@@ -20,30 +23,41 @@ module Sus
20
23
  @styles[:indent] = @indent
21
24
  end
22
25
 
26
+ # @attribute [Hash] The style definitions.
23
27
  attr :styles
24
28
 
29
+ # Create a buffered output handler.
30
+ # @returns [Buffered] A new Buffered instance.
25
31
  def buffered
26
32
  Buffered.new(self)
27
33
  end
28
34
 
35
+ # Append and replay chunks from a buffer.
36
+ # @parameter buffer [Buffered] The buffer to append from.
29
37
  def append(buffer)
30
38
  buffer.each do |operation|
31
39
  self.public_send(*operation)
32
40
  end
33
41
  end
34
42
 
43
+ # @attribute [IO] The IO object to write to.
35
44
  attr :io
36
45
 
46
+ # The indentation string.
37
47
  INDENTATION = "\t"
38
48
 
49
+ # Increase indentation level.
39
50
  def indent
40
51
  @indent << INDENTATION
41
52
  end
42
53
 
54
+ # Decrease indentation level.
43
55
  def outdent
44
56
  @indent.slice!(INDENTATION)
45
57
  end
46
58
 
59
+ # Execute a block with increased indentation.
60
+ # @yields {...} The block to execute.
47
61
  def indented
48
62
  self.indent
49
63
  yield
@@ -51,33 +65,49 @@ module Sus
51
65
  self.outdent
52
66
  end
53
67
 
68
+ # @returns [Boolean] Whether the IO is interactive (a TTY).
54
69
  def interactive?
55
70
  @io.tty?
56
71
  end
57
72
 
73
+ # Get a style by key.
74
+ # @parameter key [Symbol] The style key.
75
+ # @returns [String] The style value.
58
76
  def [] key
59
77
  @styles[key]
60
78
  end
61
79
 
80
+ # Set a style by key.
81
+ # @parameter key [Symbol] The style key.
82
+ # @parameter value [String] The style value.
62
83
  def []= key, value
63
84
  @styles[key] = value
64
85
  end
65
86
 
87
+ # @returns [Array(Integer)] The terminal size [height, width] (defaults to [24, 80]).
66
88
  def size
67
89
  [24, 80]
68
90
  end
69
91
 
92
+ # @returns [Integer] The terminal width (defaults to 80).
70
93
  def width
71
94
  size.last
72
95
  end
73
96
 
97
+ # @returns [Boolean] Always returns false, as Text output doesn't support colors.
74
98
  def colors?
75
99
  false
76
100
  end
77
101
 
102
+ # Create a style string (no-op for Text output).
103
+ # @parameter foreground [Symbol, nil] The foreground color.
104
+ # @parameter background [Symbol, nil] The background color.
105
+ # @parameter attributes [Array] Additional style attributes.
106
+ # @returns [String] An empty string.
78
107
  def style(foreground, background = nil, *attributes)
79
108
  end
80
109
 
110
+ # @returns [String] An empty string (no reset needed for plain text).
81
111
  def reset
82
112
  end
83
113
 
@@ -85,6 +115,7 @@ module Sus
85
115
  # When the argument is a symbol, look up the style and inject it into the io stream.
86
116
  # When the argument is a proc/lambda, call it with self as the argument.
87
117
  # When the argument is anything else, write it directly to the io.
118
+ # @parameter arguments [Array] The arguments to write.
88
119
  def write(*arguments)
89
120
  arguments.each do |argument|
90
121
  case argument
@@ -102,7 +133,8 @@ module Sus
102
133
  end
103
134
  end
104
135
 
105
- # Print out the arguments as per {#print}, followed by the reset sequence and a newline.
136
+ # Print out the arguments as per {#write}, followed by the reset sequence and a newline.
137
+ # @parameter arguments [Array] The arguments to write.
106
138
  def puts(*arguments)
107
139
  write(*arguments)
108
140
  @io.puts(self.reset)
@@ -8,9 +8,10 @@ require "io/console"
8
8
  require_relative "text"
9
9
 
10
10
  module Sus
11
- # Styled output output.
12
11
  module Output
12
+ # Represents an XTerm-compatible output handler with color and style support.
13
13
  class XTerm < Text
14
+ # Color codes for ANSI terminal colors.
14
15
  COLORS = {
15
16
  black: 0,
16
17
  red: 1,
@@ -23,6 +24,7 @@ module Sus
23
24
  default: 9,
24
25
  }
25
26
 
27
+ # Style attribute codes for ANSI terminal attributes.
26
28
  ATTRIBUTES = {
27
29
  normal: 0,
28
30
  bold: 1,
@@ -35,14 +37,21 @@ module Sus
35
37
  hidden: 8,
36
38
  }
37
39
 
40
+ # @returns [Boolean] Always returns true, as XTerm output supports colors.
38
41
  def colors?
39
42
  true
40
43
  end
41
44
 
45
+ # @returns [Array(Integer)] The terminal size [height, width].
42
46
  def size
43
47
  @io.winsize
44
48
  end
45
49
 
50
+ # Create an ANSI escape sequence for styling.
51
+ # @parameter foreground [Symbol, nil] The foreground color name.
52
+ # @parameter background [Symbol, nil] The background color name.
53
+ # @parameter attributes [Array] Additional style attributes.
54
+ # @returns [String] An ANSI escape sequence.
46
55
  def style(foreground, background = nil, *attributes)
47
56
  tokens = []
48
57
 
@@ -61,6 +70,7 @@ module Sus
61
70
  return "\e[#{tokens.join(';')}m"
62
71
  end
63
72
 
73
+ # @returns [String] The ANSI reset sequence.
64
74
  def reset
65
75
  "\e[0m"
66
76
  end
data/lib/sus/output.rb CHANGED
@@ -11,7 +11,11 @@ require_relative "output/null"
11
11
  require_relative "output/progress"
12
12
 
13
13
  module Sus
14
+ # Represents output handlers for test results and messages.
14
15
  module Output
16
+ # Create an appropriate output handler for the given IO.
17
+ # @parameter io [IO] The IO object to write to.
18
+ # @returns [XTerm, Text] An XTerm handler if the IO is a TTY, otherwise a Text handler.
15
19
  def self.for(io)
16
20
  if io.isatty
17
21
  XTerm.new(io)
@@ -20,6 +24,9 @@ module Sus
20
24
  end
21
25
  end
22
26
 
27
+ # Create a default output handler with styling configured.
28
+ # @parameter io [IO] The IO object to write to (defaults to $stderr).
29
+ # @returns [XTerm, Text] A configured output handler.
23
30
  def self.default(io = $stderr)
24
31
  output = self.for(io)
25
32
 
@@ -47,6 +54,8 @@ module Sus
47
54
  return output
48
55
  end
49
56
 
57
+ # Create a buffered output handler.
58
+ # @returns [Buffered] A new buffered output handler.
50
59
  def self.buffered
51
60
  Buffered.new
52
61
  end
@@ -4,18 +4,28 @@
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  module Sus
7
+ # Represents a predicate that checks if a block raises an exception.
7
8
  class RaiseException
9
+ # Initialize a new RaiseException predicate.
10
+ # @parameter exception_class [Class] The exception class to expect.
11
+ # @parameter message [String, Regexp, Object | Nil] Optional message matcher.
8
12
  def initialize(exception_class = Exception, message: nil)
9
13
  @exception_class = exception_class
10
14
  @message = message
11
15
  @predicate = nil
12
16
  end
13
17
 
18
+ # Add an additional predicate to check on the exception.
19
+ # @parameter predicate [Object] The predicate to apply to the exception.
20
+ # @returns [RaiseException] Returns self for method chaining.
14
21
  def and(predicate)
15
22
  @predicate = predicate
16
23
  return self
17
24
  end
18
25
 
26
+ # Evaluate this predicate against a subject (block).
27
+ # @parameter assertions [Assertions] The assertions instance to use.
28
+ # @parameter subject [Proc] The block to evaluate.
19
29
  def call(assertions, subject)
20
30
  assertions.nested(self) do |assertions|
21
31
  begin
@@ -36,6 +46,8 @@ module Sus
36
46
  end
37
47
  end
38
48
 
49
+ # Print a representation of this predicate.
50
+ # @parameter output [Output] The output target.
39
51
  def print(output)
40
52
  output.write("raise exception")
41
53
 
@@ -54,6 +66,10 @@ module Sus
54
66
  end
55
67
 
56
68
  class Base
69
+ # Create a predicate that checks if a block raises an exception.
70
+ # @parameter exception_class [Class] The exception class to expect.
71
+ # @parameter message [String, Regexp, Object | Nil] Optional message matcher.
72
+ # @returns [RaiseException] A new RaiseException predicate.
57
73
  def raise_exception(...)
58
74
  RaiseException.new(...)
59
75
  end
data/lib/sus/receive.rb CHANGED
@@ -6,7 +6,12 @@
6
6
  require_relative "respond_to"
7
7
 
8
8
  module Sus
9
+ # Represents an expectation that a method will be called on an object.
9
10
  class Receive
11
+ # Initialize a new Receive expectation.
12
+ # @parameter base [Object] The base object (usually self from Base).
13
+ # @parameter method [Symbol] The method name to expect.
14
+ # @yields {...} Optional block that returns the value to return from the method.
10
15
  def initialize(base, method, &block)
11
16
  @base = base
12
17
  @method = method
@@ -19,46 +24,73 @@ module Sus
19
24
  @returning = block
20
25
  end
21
26
 
27
+ # Print a representation of this expectation.
28
+ # @parameter output [Output] The output target.
22
29
  def print(output)
23
30
  output.write("receive ", :variable, @method.to_s, :reset)
24
31
  end
25
32
 
33
+ # Specify that the method should be called with specific arguments.
34
+ # @parameter predicate [Object] The predicate to match against the arguments.
35
+ # @returns [Receive] Returns self for method chaining.
26
36
  def with_arguments(predicate)
27
37
  @arguments = WithArguments.new(predicate)
28
38
  return self
29
39
  end
30
40
 
41
+ # Specify that the method should be called with specific keyword options.
42
+ # @parameter predicate [Object] The predicate to match against the options.
43
+ # @returns [Receive] Returns self for method chaining.
31
44
  def with_options(predicate)
32
45
  @options = WithOptions.new(predicate)
33
46
  return self
34
47
  end
35
48
 
49
+ # Specify that the method should be called with a block.
50
+ # @parameter predicate [Object] Optional predicate to match against the block.
51
+ # @returns [Receive] Returns self for method chaining.
36
52
  def with_block(predicate = Be.new(:!=, nil))
37
53
  @block = WithBlock.new(predicate)
38
54
  return self
39
55
  end
40
56
 
57
+ # Specify that the method should be called with specific arguments and options.
58
+ # @parameter arguments [Array] The positional arguments to match.
59
+ # @parameter options [Hash] The keyword arguments to match.
60
+ # @returns [Receive] Returns self for method chaining.
41
61
  def with(*arguments, **options)
42
62
  with_arguments(Be.new(:==, arguments)) if arguments.any?
43
63
  with_options(Be.new(:==, options)) if options.any?
44
64
  return self
45
65
  end
46
66
 
67
+ # Specify that the method should be called exactly once.
68
+ # @returns [Receive] Returns self for method chaining.
47
69
  def once
48
70
  @times = Times.new(Be.new(:==, 1))
49
71
  return self
50
72
  end
51
73
 
74
+ # Specify that the method should be called exactly twice.
75
+ # @returns [Receive] Returns self for method chaining.
52
76
  def twice
53
77
  @times = Times.new(Be.new(:==, 2))
54
78
  return self
55
79
  end
56
80
 
81
+ # Specify a predicate to match against the call count.
82
+ # @parameter predicate [Object] The predicate to match against the call count.
83
+ # @returns [Receive] Returns self for method chaining.
57
84
  def with_call_count(predicate)
58
85
  @times = Times.new(predicate)
59
86
  return self
60
87
  end
61
88
 
89
+ # Specify the value to return when the method is called.
90
+ # @parameter returning [Array] Values to return. If one value, returns it directly; if multiple, returns an array.
91
+ # @yields {...} Optional block that computes the return value.
92
+ # @returns [Receive] Returns self for method chaining.
93
+ # @raises [ArgumentError] If both values and a block are provided.
62
94
  def and_return(*returning, &block)
63
95
  if block_given?
64
96
  if returning.any?
@@ -75,6 +107,9 @@ module Sus
75
107
  return self
76
108
  end
77
109
 
110
+ # Specify that the method should raise an exception when called.
111
+ # @parameter exception [Class, String] The exception class or message to raise.
112
+ # @returns [Receive] Returns self for method chaining.
78
113
  def and_raise(...)
79
114
  @returning = proc do
80
115
  raise(...)
@@ -83,6 +118,12 @@ module Sus
83
118
  return self
84
119
  end
85
120
 
121
+ # Validate the method call arguments, options, and block.
122
+ # @parameter mock [Mock] The mock instance.
123
+ # @parameter assertions [Assertions] The assertions instance.
124
+ # @parameter arguments [Array] The positional arguments.
125
+ # @parameter options [Hash] The keyword arguments.
126
+ # @parameter block [Proc, nil] The block argument.
86
127
  def validate(mock, assertions, arguments, options, block)
87
128
  return unless @arguments or @options or @block
88
129
 
@@ -93,6 +134,9 @@ module Sus
93
134
  end
94
135
  end
95
136
 
137
+ # Evaluate this expectation against a subject.
138
+ # @parameter assertions [Assertions] The assertions instance to use.
139
+ # @parameter subject [Object] The object to expect the method call on.
96
140
  def call(assertions, subject)
97
141
  assertions.nested(self) do |assertions|
98
142
  mock = @base.mock(subject)
@@ -123,19 +167,28 @@ module Sus
123
167
  end
124
168
  end
125
169
 
170
+ # @returns [Boolean] Whether the original method should be called.
126
171
  def call_original?
127
172
  @returning.nil?
128
173
  end
129
174
 
175
+ # Represents a constraint on method call arguments.
130
176
  class WithArguments
177
+ # Initialize a new WithArguments constraint.
178
+ # @parameter predicate [Object] The predicate to match against arguments.
131
179
  def initialize(predicate)
132
180
  @predicate = predicate
133
181
  end
134
182
 
183
+ # Print a representation of this constraint.
184
+ # @parameter output [Output] The output target.
135
185
  def print(output)
136
186
  output.write("with arguments ", @predicate)
137
187
  end
138
188
 
189
+ # Evaluate this constraint against arguments.
190
+ # @parameter assertions [Assertions] The assertions instance to use.
191
+ # @parameter subject [Array] The arguments to check.
139
192
  def call(assertions, subject)
140
193
  assertions.nested(self) do |assertions|
141
194
  Expect.new(assertions, subject).to(@predicate)
@@ -143,15 +196,23 @@ module Sus
143
196
  end
144
197
  end
145
198
 
199
+ # Represents a constraint on method call keyword options.
146
200
  class WithOptions
201
+ # Initialize a new WithOptions constraint.
202
+ # @parameter predicate [Object] The predicate to match against options.
147
203
  def initialize(predicate)
148
204
  @predicate = predicate
149
205
  end
150
206
 
207
+ # Print a representation of this constraint.
208
+ # @parameter output [Output] The output target.
151
209
  def print(output)
152
210
  output.write("with options ", @predicate)
153
211
  end
154
212
 
213
+ # Evaluate this constraint against options.
214
+ # @parameter assertions [Assertions] The assertions instance to use.
215
+ # @parameter subject [Hash] The options to check.
155
216
  def call(assertions, subject)
156
217
  assertions.nested(self) do |assertions|
157
218
  Expect.new(assertions, subject).to(@predicate)
@@ -159,15 +220,23 @@ module Sus
159
220
  end
160
221
  end
161
222
 
223
+ # Represents a constraint on method call block argument.
162
224
  class WithBlock
225
+ # Initialize a new WithBlock constraint.
226
+ # @parameter predicate [Object] The predicate to match against the block.
163
227
  def initialize(predicate)
164
228
  @predicate = predicate
165
229
  end
166
230
 
231
+ # Print a representation of this constraint.
232
+ # @parameter output [Output] The output target.
167
233
  def print(output)
168
234
  output.write("with block", @predicate)
169
235
  end
170
236
 
237
+ # Evaluate this constraint against a block.
238
+ # @parameter assertions [Assertions] The assertions instance to use.
239
+ # @parameter subject [Proc, nil] The block to check.
171
240
  def call(assertions, subject)
172
241
  assertions.nested(self) do |assertions|
173
242
 
@@ -176,17 +245,26 @@ module Sus
176
245
  end
177
246
  end
178
247
 
248
+ # Represents a constraint on method call count.
179
249
  class Times
180
- ONCE = Be.new(:==, 1)
250
+ # A predicate that matches at least one call.
251
+ AT_LEAST_ONCE = Be.new(:>=, 1)
181
252
 
182
- def initialize(condition = ONCE)
253
+ # Initialize a new Times constraint.
254
+ # @parameter condition [Object] The predicate to match against the call count.
255
+ def initialize(condition = AT_LEAST_ONCE)
183
256
  @condition = condition
184
257
  end
185
-
258
+
259
+ # Print a representation of this constraint.
260
+ # @parameter output [Output] The output target.
186
261
  def print(output)
187
262
  output.write("with call count ", @condition)
188
263
  end
189
264
 
265
+ # Evaluate this constraint against a call count.
266
+ # @parameter assertions [Assertions] The assertions instance to use.
267
+ # @parameter subject [Integer] The call count to check.
190
268
  def call(assertions, subject)
191
269
  assertions.nested(self) do |assertions|
192
270
  Expect.new(assertions, subject).to(@condition)
@@ -196,6 +274,10 @@ module Sus
196
274
  end
197
275
 
198
276
  class Base
277
+ # Create an expectation that a method will be called.
278
+ # @parameter method [Symbol] The method name to expect.
279
+ # @yields {...} Optional block that returns the value to return from the method.
280
+ # @returns [Receive] A new Receive expectation.
199
281
  def receive(method, &block)
200
282
  Receive.new(self, method, &block)
201
283
  end