module NRSER::RSpex::ExampleGroup

Definitions

Public Instance Methods

ATTRIBUTE(symbol, **metadata, &body)

BOLDER name

Alias for: describe_attribute
CALLED(&body)

new / bold name

Alias for: describe_called
CALLED_WITH(*args, &body)

New / bold name

CASE(*description, where: {})
Alias for: describe_case
CLASS(klass, *description, bind_subject: true, **metadata, &body)
Alias for: describe_class
INSTANCE(*constructor_args, &body)
Alias for: describe_instance
MESSAGE(*args, &body)
Alias for: describe_message
METHOD(method, *description, bind_subject: nil, **metadata, &body)
Alias for: describe_method
SECTION(*description, **metadata, &body)

BOLD NAME!

Alias for: describe_section
SETUP(*description, **metadata, &body)
Alias for: describe_setup
SPEC_FILE(description: nil, spec_path:, bind_subject: true, **metadata, &body)
Alias for: describe_spec_file
SUBJECT(subject, **metadata, &body)

Short name

Alias for: describe_subject
WHEN(*description, **bindings, &body)
Alias for: describe_when
_when(*description, **bindings, &body)

Short names (need `_` pre 'cause of `when` Ruby keyword, and suffix fucks up auto-indent in Atom/VSCode)

Alias for: describe_when
called(&body)
Alias for: describe_called
called_with(*args, &body)

Short / old name

context_where(*description, **bindings, &body)

Old name (used to be different method)

Alias for: describe_when
describe_attr(symbol, **metadata, &body)

Shorter name

Alias for: describe_attribute
describe_attribute(symbol, **metadata, &body) click to toggle source

Describe an attribute of the parent subject.

@return [void]

Calls superclass method
# File lib/nrser/rspex/example_group/describe_attribute.rb, line 10
def describe_attribute symbol, **metadata, &body
  describe_x \
    NRSER::RSpex::Format.md_code_quote( "##{ symbol }" ),
    type: :attribute,
    metadata: metadata,
    subject_block: -> {
      super().public_send symbol
    },
    &body
end
Also aliased as: describe_attr, ATTRIBUTE
describe_called(&body) click to toggle source

Version of {#describe_called_with} for when you have no arguments.

@param [#call] body

Block to execute in the context of the example group after refining
the subject.

@return [void]

Calls superclass method
# File lib/nrser/rspex/example_group/describe_called_with.rb, line 49
def describe_called &body
  describe_x Args(),
    type: :called_with,
    subject_block: -> { super().call },
    &body
end
Also aliased as: called, when_called, CALLED
describe_called_with(*args, &body) click to toggle source

Create a new {RSpec.describe} section where the subject is set by calling the parent subject with `args` and evaluate `block` in it.

@example

describe "hi sayer" do
  subject{ ->( name ) { "Hi #{ name }!" } }

  describe_called_with 'Mom' do
    it { is_expected.to eq 'Hi Mom!' }
  end
end

@param [Array] args

Arguments to call `subject` with to produce the new subject.

@param [#call] body

Block to execute in the context of the example group after refining
the subject.

@return [void]

Calls superclass method
# File lib/nrser/rspex/example_group/describe_called_with.rb, line 27
def describe_called_with *args, &body
  describe_x Args(*args),
    type: :called_with,
    subject_block: -> { super().call *args },
    &body
end
Also aliased as: called_with, CALLED_WITH
describe_case(*description, where: {}) click to toggle source

@todo Document describe_use_case method.

@return [void]

# File lib/nrser/rspex/example_group/describe_case.rb, line 9
def describe_case *description, where: {}, **metadata, &body
  describe_x \
    *description,
    type: :case,
    bindings: where,
    metadata: metadata,
    &body
end
Also aliased as: use_case, CASE, describe_use_case
describe_class(klass, *description, bind_subject: true, **metadata, &body) click to toggle source

@todo Document describe_class method.

@return [void]

# File lib/nrser/rspex/example_group/describe_class.rb, line 10
def describe_class  klass,
                    *description,
                    bind_subject: true,
                    **metadata,
                    &body
  subject_block = if bind_subject
    -> { klass }
  end
  
  describe_x \
    NRSER::RSpex::Format.md_code_quote( klass.name ),
    klass.source_location,
    *description,
    type: :class,
    metadata: {
      **metadata,
      class: klass,
    },
    subject_block: subject_block,
    &body
end
Also aliased as: CLASS
describe_group(*description, **metadata, &body) click to toggle source

Describe a “group”. Doesn't really do much. Didn't end up getting used much. Probably not long for this world.

@param *description (see describe_x)

@param [Hash<Symbol, Object>] metadata

RSpec metadata to set for the example group.

See the `metadata` keyword argument to {#describe_x}.

@param &body (see describe_x)

@return (see describe_x)

# File lib/nrser/rspex/example_group/describe_group.rb, line 20
def describe_group *description, **metadata, &body
  # Pass up to {#describe_x}
  describe_x \
    *description,
    type: :group,
    metadata: metadata,
    &body
end
describe_instance(*constructor_args, &body) click to toggle source

Describe an instance of the described class by providing arguments for it's construction.

@param [Array] constructor_args

Arguments to pass to `.new` on {#described_class} to create instances.

@return [void]

# File lib/nrser/rspex/example_group/describe_instance.rb, line 13
def describe_instance *constructor_args, &body
  describe_x ".new", Args(*constructor_args),
    type: :instance,
    metadata: {
      constructor_args: constructor_args,
    },
    subject_block: -> {
      described_class.new *described_constructor_args
    },
    &body
end
Also aliased as: INSTANCE
describe_instance_method(name, **metadata, &block) click to toggle source
Calls superclass method
# File lib/nrser/rspex/example_group/describe_instance_method.rb, line 5
def describe_instance_method name, **metadata, &block
  describe(
    "##{ name }",
    type: :instance_method,
    method_name: name,
    **metadata
  ) do
    if name.is_a? Symbol
      subject { super().method name }
    end
    
    module_exec &block
  end
end
describe_message(*args, &body) click to toggle source

Describe a {NRSER::Message}. Useful when you have a message that you want to send to many receivers (see {#describe_sent_to}).

@note

Since the block is used for the example group body, if you want to
describe a message with a {NRSER::Message#block} your need to create
the message yourself and pass it as the only argument.

@see describe_x

@param [Array] args

Passed to {NRSER::Message.from} to get or create the message instance.

@param &body (see describe_x)

@return (see describe_x)

Calls superclass method
# File lib/nrser/rspex/example_group/describe_message.rb, line 32
def describe_message *args, &body
  message = NRSER::Message.from *args
  
  describe_x \
    message,
    type: :message,
    metadata: {
      message: message,
    },
    subject_block: -> { message.send_to super() },
    &body
end
Also aliased as: MESSAGE
describe_method(method, *description, bind_subject: nil, **metadata, &body) click to toggle source

Describe a method of the parent subject.

@param [Method | Symbol | String] method

The method being described:

1.  {Method} instance - used directly.

2.  {Symbol}, {String} -

@return [void]

Calls superclass method
# File lib/nrser/rspex/example_group/describe_method.rb, line 17
def describe_method method,
                    *description,
                    bind_subject: nil,
                    **metadata,
                    &body
  case method
  when Method
    method_name = method.name
    subject_block = -> { method }
    bind_subject = true
    name_string = NRSER::RSpex::Format.md_code_quote \
      "#{ method.receiver }.#{ method.name }"
  
  when Symbol, String
    method_name = method

    # Due to legacy, we only auto-bind if the name is a symbol
    #
    # TODO  Get rid of this
    #
    bind_subject = method_name.is_a?( Symbol ) if bind_subject.nil?

    subject_block = if bind_subject
      -> { super().method method_name }
    end

    name_prefix = if  self.respond_to?( :metadata ) &&
                      self.metadata.key?( :constructor_args )
      '#'
    else
      '.'
    end
  
    method = if self.try( :metadata )
      getter = if self.metadata.key?( :constructor_args )
        :instance_method
      else
        :method
      end
      
      target = self.metadata[:class] || self.metadata[:module]
      
      if target
        begin
          target.public_send getter, method_name
        rescue
          nil
        end
      end
    end
  
    name_string = NRSER::RSpex::Format.md_code_quote \
      "#{ name_prefix }#{ method_name }"
  
  else
    raise NRSER::TypeError.new \
      "Expected Method, Symbol or String for `method_name`, found",
      method_name
    
  end # case method_arg
  
  # Create the RSpec example group context
  describe_x \
    name_string,
    NRSER::Meta::Source::Location.new( method ),
    *description,
    type: :method,
    metadata: {
      **metadata,
      method_name: method_name,
    },
    bind_subject: bind_subject,
    subject_block: subject_block,
    &body
end
Also aliased as: METHOD
describe_module(mod, bind_subject: true, **metadata, &body) click to toggle source
# File lib/nrser/rspex/example_group/describe_module.rb, line 6
def describe_module mod, bind_subject: true, **metadata, &body
  describe_x \
    mod,
    type: :module,
    metadata: {
      module: mod,
      **metadata,
    },
    bind_subject: bind_subject,
    subject_block: -> { mod },
    &body
end
describe_response_to(*args, &body) click to toggle source

Describe the response of the subject to a {NRSER::Message}.

Pretty much a short-cut for nesting {#describe_method} / {#describe_called_with}. Meh.

@param [Array] args

Passed to {NRSER::Message.from} to get or create the message instance.

@param &body (see describe_x)

@return (see describe_x)

Calls superclass method
# File lib/nrser/rspex/example_group/describe_response_to.rb, line 18
def describe_response_to *args, &body
  msg = NRSER::Message.from *args
  
  # Pass up to {#describe_x}
  describe_x \
    msg,
    type: :response_to,
    subject_block: -> { msg.send_to super() },
    &body
end
Also aliased as: describe_return_value
describe_return_value(*args, &body)

Old name

describe_section(*description, **metadata, &body) click to toggle source

Describe a “section”. Just like {RSpec.describe} except it:

  1. Expects a string title.

  2. Prepends a little section squiggle `§` to the title so sections are easier to pick out visually.

  3. Adds `type: :section` metadata.

@param *description (see describe_x)

@param [Hash<Symbol, Object>] metadata

RSpec metadata to set for the example group.

See the `metadata` keyword argument to {#describe_x}.

@param &body (see describe_x)

@return (see describe_x)

# File lib/nrser/rspex/example_group/describe_section.rb, line 26
def describe_section *description, **metadata, &body
  # Pass up to {#describe_x}
  describe_x \
    *description,
    type: :section,
    metadata: metadata,
    &body
end
Also aliased as: describe_topic, SECTION
describe_sent_to(receiver, publicly: true, bind_subject: true, &body) click to toggle source

For use when `subject` is a {NRSER::Message}. Create a new context for the `receiver` where the subject is the result of sending that message to the receiver.

@param [Object] receiver

Object that will receive the message to create the new subject.

If it's a {Wrapper} it will be unwrapped in example contexts of the
new example group.

@param [Boolean] publicly

Send message publicly via {Object#public_send} (default) or privately
via {Object.send}.

@param bind_subject: (see describe_x) @param &body (see describe_x)

@return (see describe_x)

Calls superclass method
# File lib/nrser/rspex/example_group/describe_sent_to.rb, line 25
def describe_sent_to  receiver,
                      publicly: true,
                      bind_subject: true,
                      &body
  mode = if publicly
    "publicly"
  else
    "privately"
  end
  
  describe_x \
    receiver,
    "(#{ mode })",
    type: :sent_to,
    bind_subject: bind_subject,
    subject_block: -> {
      super().send_to \
        unwrap( receiver, context: self ),
        publicly: publicly
    },
    &body
end
Also aliased as: sent_to
describe_setup(*description, **metadata, &body) click to toggle source

Setup describes what's going to be done in all child examples.

It's where you setup your `subject`, usually depending on `let` bindings that are provided in the children.

@return [void]

# File lib/nrser/rspex/example_group/describe_setup.rb, line 12
def describe_setup *description, **metadata, &body
  describe_x \
    *description,
    type: :setup,
    metadata: metadata,
    &body
end
Also aliased as: setup, SETUP
describe_source_file(path, *description, **metadata, &body) click to toggle source

Create an example group covering a source file.

Useful for when method implementations are spread out across multiple files but you want to group examples by the source file they're in.

@note

Honestly, now that modules, classes and methods described through RSpex
add their source locations, this is not all that useful. But it was
there from before that, which is why for the moment it's still here.

@see describe_x

@param [String | Pathname] path

File path.

@param *description (see describe_x)

@param [Hash<Symbol, Object>] metadata

RSpec metadata to set for the example group.

See the `metadata` keyword argument to {#describe_x}.

A `file` key is added pointed to the {Pathname} for `path` before
passing up to {#describe_x}.

@param &body (see describe_x)

@return (see describe_x)

# File lib/nrser/rspex/example_group/describe_source_file.rb, line 35
def describe_source_file path, *description, **metadata, &body
  path = path.to_pn
  
  describe_x \
    path,
    *description,
    type: :source_file,
    metadata: {
      source_file: path,
      **metadata,
    },
    &body
end
describe_spec_file(description: nil, spec_path:, bind_subject: true, **metadata, &body) click to toggle source

EXPERIMENTAL

Example group helper for use at the top level of each spec file to set a bunch of stuff up and build a helpful description.

@todo

This is totally just a one-off right now... would need to be
generalized quite a bit...

1.  Extraction of module, class, etc from metadata should be flexible

2.  Built description would need to be conditional on what metadata
    was found.

@param [String] description

A description of the spec file to add to the RSpec description.

@param [String] spec_path

The path to the spec file (just feed it `__FILE__`).

Probably possible to extract this somehow without having to provide it?

@return (see describe_x)

# File lib/nrser/rspex/example_group/describe_spec_file.rb, line 51
def describe_spec_file  description: nil,
                        spec_path:,
                        bind_subject: true,
                        **metadata,
                        &body
  
  chain = []
  
  [
    :source_file,
    :module,
    :class,
    :instance,
    :method,
    :instance_method,
    :called_with,
    :attribute,
  ].each do |type|
    if data = metadata.delete( type )
      chain << [type, data]
    end
  end
  
  describe_x_body = if chain.empty?
    body
  else
    -> { dive_x *chain, bind_subject: bind_subject, &body }
  end
  
  describe_x \
    NRSER::RSpex.dot_rel_path( spec_path ),
    *description,
    type: :spec_file,
    metadata: metadata,
    &describe_x_body
  
end
Also aliased as: SPEC_FILE
describe_subject(subject, **metadata, &body) click to toggle source

Define a example group binding a subject.

@note Experimental - only used in Rash at the moment. I've wanted something

like this to make binding subject less noisy, but I'm not sure this is
exactly it yet...

@see describe_x

@param [Object] subject

The value to bind as the subject. May be wrapped.

@param [Hash<Symbol, Object>] metadata

Optional metadata for the example group.

@param &body (see describe_x)

@return (see describe_x)

# File lib/nrser/rspex/example_group/describe_subject.rb, line 34
def describe_subject subject, **metadata, &body
  describe_x \
    subject,
    type: :subject,
    metadata: metadata,
    subject_block: -> { unwrap subject, context: self },
    &body
end
Also aliased as: SUBJECT
describe_topic(*description, **metadata, &body)

Old name

Alias for: describe_section
describe_use_case(*description, where: {})

Older name

Alias for: describe_case
describe_when(*description, **bindings, &body) click to toggle source

Define a example group with the keyword args as bindings.

@see describe_x

@param *description (see describe_x)

@param [Hash<Symbol, Object>] bindings

See the `bindings` keyword arg in {#describe_x}.

@param &body (see describe_x)

@return (see describe_x)

# File lib/nrser/rspex/example_group/describe_when.rb, line 19
def describe_when *description, **bindings, &body
  describe_x \
    *description,
    type: :when,
    bindings: bindings,
    &body
end
Also aliased as: context_where, _when, WHEN
describe_x(*description, type:, metadata: {}) click to toggle source

The core, mostly internal method that all RSpex's description methods lead back too (or should / will when refactoring is done).

Keyword options are explicitly broken out in this method, versus the sugary ones that call it, so `metadata` can be set without restriction (save the `type` key, which is also it's own keyword here). You can use this method if you want the RSpex functionality but absolutely have to set some metadata key that we use for something else.

@param [Array] description

Optional list of elements that compose the custom description.

Will be passed to {NRSER::RSpex::Format.description} to produce the
string value that is in turn passed to {RSpec.describe}.

@param [Symbol] type

The RSpex "type" of the example group, which is used to determine the
prefix of the final description and is assigned to the `:type` metadata
key.

@param [Hash<Symbol, Object>] metadata

[RSpec metadata][] to add to the new example group.

In addition to the keys RSpec will reject, we prohibit `:type` *unless*
it is the same as the `type` keyword argument or `nil`.

In either of these cases, the `type` keyword arg will be used for the new
example group's `:type` metadata value.

[RSpec metadata]: https://relishapp.com/rspec/rspec-core/docs/metadata/user-defined-metadata

@param [Hash<Symbol, Object>] bindings

Name to value pairs to bind in the new example group.

All values will be bound at the example group and example levels -
though if they are {Wrapper}, that wrapper will be available at the
group level, while they will be automatically unwrapped at the
example level (as the requisite context is available there).

@param [Boolean] bind_subject

When `true` (and there is a `subject_block`) bind the `subject` inside
the new example group.

@return [void]

# File lib/nrser/rspex/example_group/describe_x.rb, line 52
  def describe_x  *description,
                  type:,
                  metadata: {},
                  bindings: {},
                  add_binding_desc: true,
                  bind_subject: true,
                  subject_block: nil,
                  &body
    
    # Check that `metadata` doesn't have a `:type` value too... although we
    # allow it if's equal to `type` or `nil` 'cause why not I guess?
    #
    if  metadata.key?( :type ) &&
        metadata[:type] != nil &&
        metadata[:type] != type
      raise ArgumentError.new binding.erb <<-END
        `metadata:` keyword argument may not have a `:type` key that conflicts
        with the `type:` keyword argument.
        
        Received:
          `type`:
          
              <%= type.inspect %>
          
          `metadata[:type]`:
          
              <%= metadata[:type].pretty_inspect %>
        
      END
    end
    
    # Add description of the bindings, if we have any and were told to
    unless bindings.empty? || add_binding_desc == false
      bindings_desc =  NRSER::RSpex::Format.md_code_quote \
        bindings.map { |name, value|
          "#{ name } = #{ value.inspect }"
        }.join( '; ' )
      
      if description.empty?
        description = bindings_desc
      else
        description << "(" + bindings_desc + ")"
      end
    end
    
    # Call up to RSpec's `#describe` method
    describe(
      NRSER::RSpex::Format.description( *description, type: type ),
      **metadata,
      type: type,
    ) do
      if subject_block && bind_subject
        subject &subject_block
      end
      
      # Bind bindings
      unless bindings.empty?
        bindings.each { |name, value|
          # Example-level binding
          let( name ) { unwrap value, context: self }
          
          # Example group-level binding (which may return a {Wrapper} that
          # of course can not be unwrapped at the group level)
          define_singleton_method( name ) { value }
        }
      end
      
      module_exec &body
    end # describe
    
  end
Also aliased as: describe_x_type
describe_x_type(*description, type:, metadata: {})
Alias for: describe_x
dive_x(current, *rest, **kwds, &body) click to toggle source
# File lib/nrser/rspex/example_group/describe_spec_file.rb, line 7
def dive_x current, *rest, **kwds, &body
  type, data = current
  
  method_name = "describe_#{ type }"
  
  block = if rest.empty?
    body
  else
    -> { dive_x *rest, &body }
  end
  
  begin
    public_send method_name, data, **kwds, &block
  rescue NoMethodError => error
    pp self.methods
    raise error
  end
end
sent_to(receiver, publicly: true, bind_subject: true, &body)

Aliases to other names I was using at first… not preferring their use at the moment.

Alias for: describe_sent_to
setup(*description, **metadata, &body)
Alias for: describe_setup
use_case(*description, where: {})
Alias for: describe_case
when_called(&body)
Alias for: describe_called