Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include context safety #42

Merged
merged 3 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions lib/sus/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,57 @@ def each(&block)
end
end
end

# Include an around method to the context class, that invokes the given block before running the test.
#
# Before hooks are called in the reverse order they are defined, in other words the last defined before hook is called first.
#
# @parameter hook [Proc] The block to execute before each test.
def before(&hook)
wrapper = Module.new

wrapper.define_method(:around) do |&block|
instance_exec(&hook)
super(&block)
end

self.include(wrapper)
end

# Include an around method to the context class, that invokes the given block after running the test.
#
# After hooks are called in the order they are defined, in other words the last defined after hook is called last.
#
# @parameter hook [Proc] The block to execute after each test. An `error` argument is passed if the test failed with an exception.
def after(&hook)
wrapper = Module.new

wrapper.define_method(:around) do |&block|
error = nil

super(&block)
rescue => error
raise
ensure
instance_exec(error, &hook)
end

self.include(wrapper)
end

# Add an around hook to the context class.
#
# Around hooks are called in the reverse order they are defined.
#
# The top level `around` implementation invokes before and after hooks.
#
# @paremeter block [Proc] The block to execute around each test.
def around(&block)
wrapper = Module.new

wrapper.define_method(:around, &block)

self.include(wrapper)
end
end
end
4 changes: 2 additions & 2 deletions lib/sus/it_behaves_like.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ def self.build(parent, shared, arguments = nil, unique: false, &block)
base.description = shared.name
base.identity = Identity.nested(parent.identity, base.description, unique: unique)
base.set_temporary_name("#{self}[#{base.description}]")

# User provided block is evaluated first, so that it can provide default behaviour for the shared context:
if block_given?
base.class_exec(*arguments, &block)
end

base.class_exec(*arguments, &shared.block)
return base
end
Expand Down
10 changes: 9 additions & 1 deletion lib/sus/shared.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ module Shared
attr_accessor :block

def self.build(name, block)
base = Class.new
base = Module.new
base.extend(Shared)
base.name = name
base.block = block

return base
end

def included(base)
base.class_exec(&self.block)
end

def prepended(base)
base.class_exec(&self.block)
end
end

def self.Shared(name, &block)
Expand Down
35 changes: 32 additions & 3 deletions test/sus/include_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,47 @@
# Released under the MIT License.
# Copyright, 2023, by Samuel Williams.

AThing = Sus::Shared("a thing") do |key, value: 42|
AContextWithArguments = Sus::Shared("a context with arguments") do |key, value: 42|
let(:a_thing) {{key => value}}
end

AContextWithHooks = Sus::Shared("a context with hooks") do
before do
events << :shared_before
end

after do
events << :shared_after
end

around do |&block|
events << :shared_around_before
super(&block)
end
end

describe Sus::Context do
with '.include_context' do
with "a shared context with an option" do
include_context AThing, :key, value: 42
include_context AContextWithArguments, :key, value: 42

it "can include a shared context" do
it "can include a shared context with arguments" do
expect(a_thing).to be == {:key => 42}
end
end

with "a shared context with arguments" do
let(:events) {Array.new}

include AContextWithHooks

before do
events << :example_before
end

it "can include a shared context" do
expect(events).to be == [:example_before, :shared_around_before, :shared_before]
end
end
end
end
Loading