diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f084e13..befbf99 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require 'transition_through' require 'verbose_migrations' require 'active_support' require 'active_record' @@ -7,9 +8,6 @@ require 'sqlite3' require 'logger' -require 'rspec/mocks/standalone' -require 'prism' - ActiveRecord::Base.logger = ActiveSupport::TaggedLogging.new( ActiveSupport::Logger.new(STDOUT), ) @@ -21,95 +19,6 @@ RSpec::Matchers.define_negated_matcher :not_change, :change -module TransitionThrough - ## - # TransitionExpression walks a Prism AST until we find a transition expression, e.g.: - # - # expect { ... }.to transition { ... }.through [...] - # - # Returns the transition state in the transition block. - class TransitionExpression < Prism::Visitor - Result = Data.define(:receiver, :method_name) - - attr_reader :at, :result - - def initialize(at:) = @at = at - - def visit_call_node(node) - case node - in name: :transition, block: Prism::BlockNode(body: Prism::Node(body: [Prism::CallNode(receiver:, name: method_name)]), location:) if location.start_line == at - @result = Result.new(receiver:, method_name:) - else - super - end - end - end - - class Matcher - include RSpec::Matchers, RSpec::Matchers::Composable, RSpec::Mocks::ExampleMethods - - attr_reader :state_block - - def initialize(state_block) - @state_block = state_block - @expected_states = [] - @actual_states = [] - end - - def supports_block_expectations? = true - def matches?(expect_block) - path, start_line = state_block.source_location - - # walk the ast until we find our transition expression - exp = TransitionExpression.new(at: start_line) - ast = Prism.parse_file(path) - - ast.value.accept(exp) - - # get the actual transitioning object from the state block's binding - receiver = state_block.binding.eval(exp.result.receiver.name.to_s) - - # get the receivers method names for stubbing - setter = receiver.method(:"#{exp.result.method_name}=") - getter = receiver.method(exp.result.method_name) - - # record initial state - @actual_states = [getter.call] - - # stub the setter so that we can track state transitions - allow(receiver).to receive(setter.name) do |value| - @actual_states << value - - setter.call(value) - end - - # call the expect block - expect_block.call - - # assert states match - @actual_states == @expected_states - end - - def through(*values) - @expected_states = values.flatten(1) - - self - end - - def failure_message - "expected block to transition through #{@expected_states.inspect} but it transitioned through #{@actual_states.inspect}" - end - - def failure_message_when_negated - "expected block not to transition through #{@expected_states.inspect} but it did" - end - end - - module Methods - def transition(&block) = Matcher.new(block) - end -end - RSpec.configure do |config| config.include TransitionThrough::Methods diff --git a/verbose_migrations.gemspec b/verbose_migrations.gemspec index d56d7fb..8ea466f 100644 --- a/verbose_migrations.gemspec +++ b/verbose_migrations.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'rails', '>= 6.0' spec.add_development_dependency 'rspec-rails' + spec.add_development_dependency 'transition_through', '1.0.0.pre.beta.1' spec.add_development_dependency 'sqlite3', '~> 1.4' spec.add_development_dependency 'prism' end