Cleanup and add docs

This commit is contained in:
Michael Miller 2021-07-17 14:01:27 -06:00
parent 571bc7d8a5
commit 1b53607f8e
No known key found for this signature in database
GPG key ID: FB9F12F7C646A4AD
7 changed files with 99 additions and 19 deletions

View file

@ -1,12 +1,25 @@
require "./context"
require "./example"
require "./location"
require "./metadata"
require "./node_builder" require "./node_builder"
module Spectator module Spectator
# Constructs examples.
# Call `#build` to produce an `Example`.
class ExampleBuilder < NodeBuilder class ExampleBuilder < NodeBuilder
# Creates the builder.
# A proc provided by *context_builder* is used to create a unique `Context` for each example produced by `#build`.
# The *entrypoint* indicates the proc used to invoke the test code in the example.
# The *name*, *location*, and *metadata* will be applied to the `Example` produced by `#build`.
def initialize(@context_builder : -> Context, @entrypoint : Example ->, def initialize(@context_builder : -> Context, @entrypoint : Example ->,
@name : String? = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new) @name : String? = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new)
end end
def build(parent) # Constructs an example with previously defined attributes and context.
# The *parent* is an already constructed example group to nest the new example under.
# It can be nil if the new example won't have a parent.
def build(parent = nil)
context = @context_builder.call context = @context_builder.call
Example.new(context, @entrypoint, @name, @location, parent, @metadata) Example.new(context, @entrypoint, @name, @location, parent, @metadata)
end end

View file

@ -1,6 +1,16 @@
require "./example_group"
require "./example_group_hook"
require "./example_hook"
require "./example_procsy_hook"
require "./label"
require "./location"
require "./metadata"
require "./node_builder" require "./node_builder"
module Spectator module Spectator
# Progressively constructs an example group.
# Hooks and builders for child nodes can be added over time to this builder.
# When done, call `#build` to produce an `ExampleGroup`.
class ExampleGroupBuilder < NodeBuilder class ExampleGroupBuilder < NodeBuilder
@children = [] of NodeBuilder @children = [] of NodeBuilder
@before_all_hooks = [] of ExampleGroupHook @before_all_hooks = [] of ExampleGroupHook
@ -9,6 +19,9 @@ module Spectator
@after_each_hooks = [] of ExampleHook @after_each_hooks = [] of ExampleHook
@around_each_hooks = [] of ExampleProcsyHook @around_each_hooks = [] of ExampleProcsyHook
# Creates the builder.
# Initially, the builder will have no children and no hooks.
# The *name*, *location*, and *metadata* will be applied to the `ExampleGroup` produced by `#build`.
def initialize(@name : Label = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new) def initialize(@name : Label = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new)
end end
@ -68,19 +81,30 @@ module Spectator
@around_each_hooks << ExampleProcsyHook.new(label: "around_each", &block) @around_each_hooks << ExampleProcsyHook.new(label: "around_each", &block)
end end
# Constructs an example group with previously defined attributes, children, and hooks.
# The *parent* is an already constructed example group to nest the new example group under.
# It can be nil if the new example group won't have a parent.
def build(parent = nil) def build(parent = nil)
ExampleGroup.new(@name, @location, parent, @metadata).tap do |group| ExampleGroup.new(@name, @location, parent, @metadata).tap do |group|
@before_all_hooks.each { |hook| group.add_before_all_hook(hook) } apply_hooks(group)
@before_each_hooks.each { |hook| group.add_before_each_hook(hook) }
@after_all_hooks.each { |hook| group.add_after_all_hook(hook) }
@after_each_hooks.each { |hook| group.add_after_each_hook(hook) }
@around_each_hooks.each { |hook| group.add_around_each_hook(hook) }
@children.each(&.build(group)) @children.each(&.build(group))
end end
end end
# Adds a child builder to the group.
# The *builder* will have `NodeBuilder#build` called on it from within `#build`.
# The new example group will be passed to it.
def <<(builder) def <<(builder)
@children << builder @children << builder
end end
# Adds all previously configured hooks to an example group.
private def apply_hooks(group)
@before_all_hooks.each { |hook| group.add_before_all_hook(hook) }
@before_each_hooks.each { |hook| group.add_before_each_hook(hook) }
@after_all_hooks.each { |hook| group.add_after_all_hook(hook) }
@after_each_hooks.each { |hook| group.add_after_each_hook(hook) }
@around_each_hooks.each { |hook| group.add_around_each_hook(hook) }
end
end end
end end

View file

@ -1,9 +1,22 @@
require "./example_group" require "./example_group"
require "./label"
require "./location"
require "./metadata"
module Spectator module Spectator
# Collection of examples and sub-groups for a single iteration of an iterative example group.
class ExampleGroupIteration(T) < ExampleGroup class ExampleGroupIteration(T) < ExampleGroup
# Item for this iteration of the example groups.
getter item : T getter item : T
# Creates the example group iteration.
# The element for the current iteration is provided by *item*.
# The *name* describes the purpose of the group.
# It can be a `Symbol` to describe a type.
# This is typically a stringified form of *item*.
# The *location* tracks where the group exists in source code.
# This group will be assigned to the parent *group* if it is provided.
# A set of *metadata* can be used for filtering and modifying example behavior.
def initialize(@item : T, name : Label = nil, location : Location? = nil, def initialize(@item : T, name : Label = nil, location : Location? = nil,
group : ExampleGroup? = nil, metadata : Metadata = Metadata.new) group : ExampleGroup? = nil, metadata : Metadata = Metadata.new)
super(name, location, group, metadata) super(name, location, group, metadata)

View file

@ -1,31 +1,48 @@
require "./example_group"
require "./example_group_builder" require "./example_group_builder"
require "./example_group_iteration" require "./example_group_iteration"
require "./location"
require "./metadata"
module Spectator module Spectator
# Progressively constructs an iterative example group.
# Hooks and builders for child nodes can be added over time to this builder.
# When done, call `#build` to produce an `ExampleGroup` with nested `ExampleGroupIteration` instances.
class IterativeExampleGroupBuilder(T) < ExampleGroupBuilder class IterativeExampleGroupBuilder(T) < ExampleGroupBuilder
# Creates the builder.
# Initially, the builder will have no children and no hooks.
# The *name*, *location*, and *metadata* will be applied to the `ExampleGroup` produced by `#build`.
# The *collection* is the set of items to create sub-nodes for.
# The *iterator* is an optional name given to a single item in the collection.
def initialize(@collection : Enumerable(T), name : String? = nil, @iterator : String? = nil, def initialize(@collection : Enumerable(T), name : String? = nil, @iterator : String? = nil,
location : Location? = nil, metadata : Metadata = Metadata.new) location : Location? = nil, metadata : Metadata = Metadata.new)
super(name, location, metadata) super(name, location, metadata)
end end
# Constructs an iterative example group with previously defined attributes, children, and hooks.
# The *parent* is an already constructed example group to nest the new example group under.
# It can be nil if the new example group won't have a parent.
def build(parent = nil) def build(parent = nil)
ExampleGroup.new(@name, @location, parent, @metadata).tap do |group| ExampleGroup.new(@name, @location, parent, @metadata).tap do |group|
@before_all_hooks.each { |hook| group.add_before_all_hook(hook) } # Hooks are applied once to the outer group,
@before_each_hooks.each { |hook| group.add_before_each_hook(hook) } # instead of multiple times for each inner group (iteration).
@after_all_hooks.each { |hook| group.add_after_all_hook(hook) } apply_hooks(group)
@after_each_hooks.each { |hook| group.add_after_each_hook(hook) }
@around_each_hooks.each { |hook| group.add_around_each_hook(hook) }
@collection.each do |item| @collection.each do |item|
name = if iterator = @iterator ExampleGroupIteration.new(item, iteration_name(item), @location, group).tap do |iteration|
"#{iterator}: #{item.inspect}"
else
item.inspect
end
ExampleGroupIteration.new(item, name, @location, group).tap do |iteration|
@children.each(&.build(iteration)) @children.each(&.build(iteration))
end end
end end
end end
end end
# Constructs the name of an example group iteration.
private def iteration_name(item)
if iterator = @iterator
"#{iterator}: #{item.inspect}"
else
item.inspect
end
end
end end
end end

View file

@ -1,5 +1,6 @@
module Spectator module Spectator
abstract class NodeBuilder abstract class NodeBuilder
abstract def build(parent) # Produces a node for a spec.
abstract def build(parent = nil)
end end
end end

View file

@ -1,11 +1,21 @@
require "./example"
require "./location"
require "./metadata"
require "./node_builder" require "./node_builder"
module Spectator module Spectator
# Constructs pending examples.
# Call `#build` to produce an `Example`.
class PendingExampleBuilder < NodeBuilder class PendingExampleBuilder < NodeBuilder
# Creates the builder.
# The *name*, *location*, and *metadata* will be applied to the `Example` produced by `#build`.
def initialize(@name : String? = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new) def initialize(@name : String? = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new)
end end
def build(parent) # Constructs an example with previously defined attributes.
# The *parent* is an already constructed example group to nest the new example under.
# It can be nil if the new example won't have a parent.
def build(parent = nil)
Example.pending(@name, @location, parent, @metadata) Example.pending(@name, @location, parent, @metadata)
end end
end end

View file

@ -71,6 +71,8 @@ module Spectator
# #
# The *collection* is the set of items to iterate over. # The *collection* is the set of items to iterate over.
# Child nodes in this group will be executed once for every item in the collection. # Child nodes in this group will be executed once for every item in the collection.
# The *name* should be a string representation of *collection*.
# The *iterator* is an optional name given to a single item in *collection*.
# #
# The *location* optionally defined where the group originates in source code. # The *location* optionally defined where the group originates in source code.
# #