diff --git a/src/spectator/dsl/builder.cr b/src/spectator/dsl/builder.cr index 3054f18..9ebedaf 100644 --- a/src/spectator/dsl/builder.cr +++ b/src/spectator/dsl/builder.cr @@ -21,6 +21,15 @@ module Spectator::DSL @@builder.start_group(*args) end + # Defines a new iterative example group and pushes it onto the group stack. + # Examples and groups defined after calling this method will be nested under the new group. + # The group will be finished and popped off the stack when `#end_example` is called. + # + # See `Spec::Builder#start_iterative_group` for usage details. + def start_iterative_group(*args) + @@builder.start_iterative_group(*args) + end + # Completes a previously defined example group and pops it off the group stack. # Be sure to call `#start_group` and `#end_group` symmetically. # diff --git a/src/spectator/iterative_example_group.cr b/src/spectator/iterative_example_group.cr new file mode 100644 index 0000000..cf384a0 --- /dev/null +++ b/src/spectator/iterative_example_group.cr @@ -0,0 +1,37 @@ +require "./example_group" +require "./node" + +module Spectator + # Collection of examples and sub-groups executed multiple times. + # Each sub-node is executed once for each item in a given collection. + class IterativeExampleGroup(T) < ExampleGroup + # Creates the iterative example group. + # The *collection* is a list of items to iterative over each sub-node over. + # 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(collection : Enumerable(T), location : Location? = nil, + group : ExampleGroup? = nil, metadata : Metadata = Metadata.new) + super() + + @nodes = collection.map do |item| + Iteration.new(item, location, group, metadata).as(Node) + end + end + + # Adds the specified *node* to the group. + # Assigns the node to this group. + # If the node already belongs to a group, + # it will be removed from the previous group before adding it to this group. + def <<(node : Node) + @nodes.each { |child| child.as(Iteration(T)) << node.dup } + end + + private class Iteration(T) < ExampleGroup + def initialize(@item : T, location : Location? = nil, + group : ExampleGroup? = nil, metadata : Metadata = Metadata.new) + super(@item.inspect, location, group, metadata) + end + end + end +end diff --git a/src/spectator/spec/builder.cr b/src/spectator/spec/builder.cr index f1b401d..8bee550 100644 --- a/src/spectator/spec/builder.cr +++ b/src/spectator/spec/builder.cr @@ -2,6 +2,7 @@ require "../config" require "../example" require "../example_context_method" require "../example_group" +require "../iterative_example_group" require "../spec" require "../metadata" @@ -65,6 +66,27 @@ module Spectator end end + # Defines a new iterative example group and pushes it onto the group stack. + # Examples and groups defined after calling this method will be nested under the new group. + # The group will be finished and popped off the stack when `#end_example` is called. + # + # 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. + # + # The *location* optionally defined where the group originates in source code. + # + # A set of *metadata* can be used for filtering and modifying example behavior. + # For instance, adding a "pending" tag will mark tests as pending and skip execution. + # + # The newly created group is returned. + # It shouldn't be used outside of this class until a matching `#end_group` is called. + def start_iterative_group(collection, location = nil, metadata = Metadata.new) : ExampleGroup + Log.trace { "Start iterative group: #{typeof(collection)} @ #{location}; metadata: #{metadata}" } + IterativeExampleGroup.new(collection, location, current_group, metadata).tap do |group| + @group_stack << group + end + end + # Completes a previously defined example group and pops it off the group stack. # Be sure to call `#start_group` and `#end_group` symmetically. #