# frozen_string_literal: true require 'factory_girl' require 'rast/parameter_generator' # Main DSL. This is the entry point of the test when running a spec. class SpecDSL include FactoryGirl::Syntax::Methods attr_accessor :subject, :rspec_methods, :execute_block, :prepare_block, :transients, :outcomes, :fixtures # # yaml-less attr_writer :variables, :exclude, :converters, :rules, :pair # @subject the sut instance # @name the sut name to be displayed with -fd def initialize(subject: nil, name: '', fixtures: [], spec_id: '', &block) @subject = subject @spec_id = spec_id # cannot derive name from subject when sut is a module. @subject_name = name || subject.class @fixtures = fixtures @transients = [] @result = nil @rspec_methods = [] instance_eval(&block) end def result(outcome) @outcome = outcome.to_s end def respond_to_missing?(*several_variants) super(several_variants) end def method_missing(method_name_symbol, *args, &block) # p "method_missing: #{method_name_symbol}" return super if method_name_symbol == :to_ary @rspec_methods << { name: method_name_symbol, args: args.first, block: block } self end # yaml-less start def variables(vars) @variables = vars end def exclude(clause) @exclude = clause end def converters(&block) @converters = instance_eval(&block) end def rules(rules) @rules = rules end def pair(pair) @pair = pair end # yaml-less end def prepare(&block) @prepare_block = block @transients end def execute(&block) @execute_block = block if @fixtures.nil? parameter_generator = ParameterGenerator.new parameter_generator.specs_config = { @spec_id => { 'variables' => @variables, 'pair' => @pair, 'converters' => @converters, 'rules' => @rules, 'exclude' => @exclude } } @fixtures = parameter_generator.generate_fixtures(spec_id: @spec_id) end @fixtures.sort_by! do |fixture| fixture[:expected_outcome] + fixture[:scenario].to_s end generate_rspecs end private def generate_rspecs main_scope = self RSpec.describe "#{@subject_name}: #{@fixtures.first[:spec].description}" do main_scope.fixtures.each do |fixture| generate_rspec( scope: main_scope, scenario: fixture[:scenario], expected: fixture[:expected_outcome] ) end end end end def generate_rspec(scope: nil, scenario: {}, expected: '') spec_params = scenario.keys.inject('') do |output, key| output += ', ' unless output == '' output + "#{key}: #{scenario[key]}" end it "[#{expected}]=[#{spec_params}]" do block_params = scenario.values @mysubject = scope.subject class << self define_method(:subject) { @mysubject } end if scope.rspec_methods.size > 0 || !scope.prepare_block.nil? instance_exec(*block_params, &scope.prepare_block) end actual = scope.execute_block.call(*block_params).to_s expect(actual).to eq(expected) end end # DSL Entry Point def spec(subject: nil, name: '', fixtures: [], spec_id: '', &block) SpecDSL.new( subject: subject, name: name, fixtures: fixtures, spec_id: spec_id, &block ) end