├── .rspec ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── spec ├── assets │ ├── symlink.txt │ ├── file.txt │ └── file2.txt ├── support │ ├── models │ │ ├── job_offer.rb │ │ ├── people │ │ │ └── actor.rb │ │ ├── machinist_model.rb │ │ ├── uuid_user.rb │ │ ├── opera.rb │ │ ├── user.rb │ │ ├── payment.rb │ │ ├── plain_ruby_class.rb │ │ └── movie.rb │ ├── uploaders │ │ └── attachment_uploader.rb │ ├── database.sample.yml │ ├── database.github.yml │ ├── database.rb │ ├── factories │ │ └── factories.rb │ └── cucumber_helper.rb ├── spec_helper.rb └── cucumber_factory │ ├── factory │ └── build_strategy_spec.rb │ └── steps_spec.rb ├── lib ├── cucumber_factory │ ├── add_steps.rb │ ├── version.rb │ ├── update_strategy.rb │ ├── switcher.rb │ ├── build_strategy.rb │ └── factory.rb └── cucumber_factory.rb ├── .gitignore ├── Rakefile ├── Gemfile.cucumber-3.1 ├── Gemfile.cucumber-3.0 ├── Gemfile.rails-8 ├── Gemfile.cucumber-10.1 ├── Gemfile.cucumber-4.1 ├── Gemfile.cucumber-5.3 ├── Gemfile.cucumber-1.3 ├── Gemfile.cucumber-2.4 ├── Gemfile.rails-7 ├── LICENSE ├── cucumber_factory.gemspec ├── Gemfile.cucumber-1.3.lock ├── Gemfile.cucumber-2.4.lock ├── .github └── workflows │ └── test.yml ├── Gemfile.cucumber-3.1.lock ├── Gemfile.cucumber-3.0.lock ├── Gemfile.rails-7.lock ├── media ├── logo.light.text.svg ├── logo.dark.text.svg ├── makandra-with-bottom-margin.dark.svg ├── makandra-with-bottom-margin.light.svg ├── logo.light.shapes.svg └── logo.dark.shapes.svg ├── Gemfile.cucumber-5.3.lock ├── Gemfile.cucumber-4.1.lock ├── Gemfile.rails-8.lock ├── Gemfile.cucumber-10.1.lock ├── CHANGELOG.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.1 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | Gemfile.rails-8 -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | Gemfile.rails-8.lock -------------------------------------------------------------------------------- /spec/assets/symlink.txt: -------------------------------------------------------------------------------- 1 | file.txt -------------------------------------------------------------------------------- /spec/assets/file.txt: -------------------------------------------------------------------------------- 1 | This is a test file. 2 | -------------------------------------------------------------------------------- /spec/assets/file2.txt: -------------------------------------------------------------------------------- 1 | This is the second test file. 2 | -------------------------------------------------------------------------------- /lib/cucumber_factory/add_steps.rb: -------------------------------------------------------------------------------- 1 | CucumberFactory::Factory.add_steps(self) 2 | -------------------------------------------------------------------------------- /spec/support/models/job_offer.rb: -------------------------------------------------------------------------------- 1 | class JobOffer < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /lib/cucumber_factory/version.rb: -------------------------------------------------------------------------------- 1 | module CucumberFactory 2 | VERSION = '2.7.0' 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/models/people/actor.rb: -------------------------------------------------------------------------------- 1 | module People 2 | class Actor < ActiveRecord::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /spec/support/models/machinist_model.rb: -------------------------------------------------------------------------------- 1 | class MachinistModel 2 | 3 | def self.make 4 | end 5 | 6 | end 7 | -------------------------------------------------------------------------------- /spec/support/models/uuid_user.rb: -------------------------------------------------------------------------------- 1 | class UuidUser < ActiveRecord::Base 2 | 3 | self.primary_key = 'id' 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/uploaders/attachment_uploader.rb: -------------------------------------------------------------------------------- 1 | class AttachmentUploader < CarrierWave::Uploader::Base 2 | storage :file 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | pkg 3 | *.gem 4 | .bundle 5 | .idea 6 | .rvmrc 7 | log/*.log 8 | spec/support/database.yml 9 | uploads/ 10 | -------------------------------------------------------------------------------- /spec/support/models/opera.rb: -------------------------------------------------------------------------------- 1 | class Opera < ActiveRecord::Base 2 | has_many :movies, as: :premiere_site, inverse_of: :premiere_site 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/database.sample.yml: -------------------------------------------------------------------------------- 1 | postgres: 2 | database: cucumber_factory_test 3 | host: localhost 4 | username: postgres 5 | password: postgres 6 | -------------------------------------------------------------------------------- /spec/support/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | 3 | has_many :reviewed_movies, :class_name => 'Movie', :foreign_key => 'reviewer_id' 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/database.github.yml: -------------------------------------------------------------------------------- 1 | postgresql: 2 | database: cucumber_factory_test 3 | host: localhost 4 | username: postgres 5 | password: postgres 6 | port: 5432 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'bundler/gem_tasks' 3 | 4 | begin 5 | require 'gemika/tasks' 6 | rescue LoadError 7 | puts 'Run `gem install gemika` for additional tasks' 8 | end 9 | 10 | task :default => 'matrix:spec' 11 | -------------------------------------------------------------------------------- /spec/support/models/payment.rb: -------------------------------------------------------------------------------- 1 | class Payment < ActiveRecord::Base 2 | class AttachmentUploader < CarrierWave::Uploader::Base 3 | storage :file 4 | end 5 | 6 | mount_uploader :attachment, AttachmentUploader 7 | 8 | if ActiveRecord::VERSION::MAJOR <= 3 9 | # Only the comment is accessible, amount isn't 10 | attr_accessible :comment 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/models/plain_ruby_class.rb: -------------------------------------------------------------------------------- 1 | class PlainRubyClass 2 | def initialize(attributes) 3 | @attributes = attributes 4 | self.class.last = self 5 | end 6 | 7 | attr_reader :attributes 8 | 9 | def self.last 10 | @last 11 | end 12 | 13 | def self.last=(instance) 14 | @last = instance 15 | end 16 | 17 | def self.reset 18 | @last = nil 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/support/models/movie.rb: -------------------------------------------------------------------------------- 1 | class Movie < ActiveRecord::Base 2 | 3 | belongs_to :prequel, :class_name => "Movie" 4 | belongs_to :reviewer, :class_name => "User" 5 | belongs_to :uuid_reviewer, :class_name => "UuidUser" 6 | belongs_to :premiere_site, polymorphic: true, required: false 7 | 8 | validate do |record| 9 | record.errors.add(:reviewer, 'may not be deleted') if record.reviewer and record.reviewer.deleted? 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /Gemfile.cucumber-3.1: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Runtime dependencies 4 | gem 'cucumber', '~> 3.1.0' 5 | gem 'activesupport', '~> 5.1.0' 6 | gem 'activerecord', '~> 5.1.0' 7 | gem 'pg' 8 | 9 | # Development dependencies 10 | gem 'rspec', '~> 3.0' 11 | gem 'rake' 12 | gem 'database_cleaner' 13 | gem 'gemika', '>= 0.8.1' 14 | 15 | # Test dependencies 16 | gem 'factory_bot' 17 | gem 'carrierwave' 18 | 19 | # Gem under test 20 | gem 'cucumber_factory', :path => '.' 21 | -------------------------------------------------------------------------------- /Gemfile.cucumber-3.0: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Runtime dependencies 4 | gem 'cucumber', '~> 3.0.0' 5 | gem 'activesupport', '~> 5.0.0' 6 | gem 'activerecord', '~> 5.0.0' 7 | gem 'pg', ' < 1' 8 | 9 | # Development dependencies 10 | gem 'rspec', '~> 3.0' 11 | gem 'rake' 12 | gem 'database_cleaner' 13 | gem 'gemika', '>= 0.8.1' 14 | 15 | # Test dependencies 16 | gem 'factory_bot' 17 | gem 'carrierwave' 18 | 19 | # Gem under test 20 | gem 'cucumber_factory', :path => '.' 21 | -------------------------------------------------------------------------------- /Gemfile.rails-8: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Runtime dependencies 4 | gem 'cucumber', '~> 9.2.1' 5 | gem 'activesupport', '~> 8.0.0' 6 | gem 'activerecord', '~> 8.0.0' 7 | gem 'pg', '> 1.2.3' 8 | 9 | # Development dependencies 10 | gem 'rspec', '~> 3.0' 11 | gem 'rake', '> 10.0' 12 | gem 'database_cleaner' 13 | gem 'gemika', '>= 0.8.1' 14 | 15 | # Test dependencies 16 | gem 'factory_bot' 17 | gem 'carrierwave' 18 | 19 | # Gem under test 20 | gem 'cucumber_factory', :path => '.' 21 | -------------------------------------------------------------------------------- /Gemfile.cucumber-10.1: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Runtime dependencies 4 | gem 'cucumber', '~> 10.1.0' 5 | gem 'activesupport', '~> 8.0.0' 6 | gem 'activerecord', '~> 8.0.0' 7 | gem 'pg', '> 1.2.3' 8 | 9 | # Development dependencies 10 | gem 'rspec', '~> 3.0' 11 | gem 'rake', '> 10.0' 12 | gem 'database_cleaner' 13 | gem 'gemika', '>= 0.8.1' 14 | 15 | # Test dependencies 16 | gem 'factory_bot' 17 | gem 'carrierwave' 18 | 19 | # Gem under test 20 | gem 'cucumber_factory', :path => '.' 21 | -------------------------------------------------------------------------------- /Gemfile.cucumber-4.1: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Runtime dependencies 4 | gem 'cucumber', '~> 4.1.0' 5 | gem 'activesupport', '~> 6.0.0' 6 | gem 'activerecord', '~> 6.0.0' 7 | gem 'pg', '> 1.2.3' 8 | 9 | # Development dependencies 10 | gem 'rspec', '~> 3.0' 11 | gem 'rake', '> 10.0' 12 | gem 'database_cleaner' 13 | gem 'gemika', '>= 0.8.1' 14 | 15 | # Test dependencies 16 | gem 'factory_bot' 17 | gem 'carrierwave' 18 | 19 | # Gem under test 20 | gem 'cucumber_factory', :path => '.' 21 | -------------------------------------------------------------------------------- /Gemfile.cucumber-5.3: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Runtime dependencies 4 | gem 'cucumber', '~> 5.3.0' 5 | gem 'activesupport', '~> 6.1.0' 6 | gem 'activerecord', '~> 6.1.0' 7 | gem 'pg', '> 1.2.3' 8 | 9 | # Development dependencies 10 | gem 'rspec', '~> 3.0' 11 | gem 'rake', '> 10.0' 12 | gem 'database_cleaner' 13 | gem 'gemika', '>= 0.8.1' 14 | 15 | # Test dependencies 16 | gem 'factory_bot' 17 | gem 'carrierwave' 18 | 19 | # Gem under test 20 | gem 'cucumber_factory', :path => '.' 21 | -------------------------------------------------------------------------------- /Gemfile.cucumber-1.3: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Runtime dependencies 4 | gem 'cucumber', '~> 1.3.20' 5 | gem 'activesupport', '~> 4.2.0' 6 | gem 'activerecord', '~> 4.2.0' 7 | gem 'pg', ' < 1' 8 | 9 | # Development dependencies 10 | gem 'rspec', '> 3.0' 11 | gem 'rake', '>=10.0.4' 12 | gem 'database_cleaner', '~>1.0.0' 13 | gem 'gemika', '>= 0.8.1' 14 | 15 | # Test dependencies 16 | gem 'factory_bot', '< 5' 17 | gem 'carrierwave', '~> 1.0' 18 | 19 | # Gem under test 20 | gem 'cucumber_factory', :path => '.' 21 | -------------------------------------------------------------------------------- /Gemfile.cucumber-2.4: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Runtime dependencies 4 | gem 'cucumber', '~> 2.4.0' 5 | gem 'activesupport', '~> 4.2.0' 6 | gem 'activerecord', '~> 4.2.0' 7 | gem 'pg', ' < 1' 8 | 9 | # Development dependencies 10 | gem 'rspec', '~> 3.0' 11 | gem 'rake' 12 | gem 'database_cleaner' 13 | gem 'gemika', '>= 0.8.1' 14 | 15 | # Test dependencies 16 | gem 'factory_bot', '< 5' # 5.0 requires Ruby >= 2.3 17 | gem 'carrierwave', '~> 1.0' # 2.0 requires Rails >= 5 and Ruby >= 2.2 18 | 19 | # Gem under test 20 | gem 'cucumber_factory', :path => '.' 21 | -------------------------------------------------------------------------------- /Gemfile.rails-7: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Former standard gems, see https://stdgems.org/ 4 | gem 'bigdecimal' # Bundled gem since Ruby 3.4 5 | gem 'mutex_m' # Bundled gem since Ruby 3.4 6 | gem 'base64' # Bundled gem since Ruby 3.4 7 | gem 'logger' # Default gem since Ruby 3.4 8 | 9 | # Runtime dependencies 10 | gem 'cucumber', '~> 9.2.1' 11 | gem 'activesupport', '~> 7.0.1' 12 | gem 'activerecord', '~> 7.0.1' 13 | gem 'pg', '> 1.2.3' 14 | 15 | # Development dependencies 16 | gem 'rspec', '~> 3.0' 17 | gem 'rake', '> 10.0' 18 | gem 'database_cleaner' 19 | gem 'gemika', '>= 0.8.1' 20 | 21 | # Test dependencies 22 | gem 'factory_bot' 23 | gem 'carrierwave' 24 | 25 | # Gem under test 26 | gem 'cucumber_factory', :path => '.' 27 | -------------------------------------------------------------------------------- /lib/cucumber_factory/update_strategy.rb: -------------------------------------------------------------------------------- 1 | module CucumberFactory 2 | 3 | class UpdateStrategy 4 | 5 | def initialize(record) 6 | @record = record 7 | end 8 | 9 | def assign_attributes(attributes) 10 | active_record_strategy(attributes) || 11 | ruby_object_strategy(attributes) 12 | end 13 | 14 | private 15 | 16 | def active_record_strategy(attributes) 17 | return unless @record.respond_to?(:save!) 18 | 19 | CucumberFactory::Switcher.assign_attributes(@record, attributes) 20 | @record.save! 21 | end 22 | 23 | def ruby_object_strategy(attributes) 24 | attributes.each do |name, value| 25 | @record.send("#{name}=".to_sym, value) 26 | end 27 | end 28 | 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/cucumber_factory.rb: -------------------------------------------------------------------------------- 1 | # Runtime dependencies 2 | require 'active_support/all' 3 | require 'active_record' 4 | 5 | require 'cucumber' 6 | if Gem::Version.new(Cucumber::VERSION) >= Gem::Version.new('3.0') 7 | require 'cucumber/glue/registry_and_more' 8 | else 9 | require 'cucumber/rb_support/rb_language' 10 | end 11 | 12 | require 'cucumber_priority' 13 | 14 | # Gem 15 | require 'cucumber_factory/build_strategy' 16 | require 'cucumber_factory/update_strategy' 17 | require 'cucumber_factory/factory' 18 | require 'cucumber_factory/switcher' 19 | 20 | module Cucumber 21 | module Factory 22 | module_function 23 | 24 | def add_steps(main) 25 | warn "Using `Cucumber::Factory.add_steps(self)` is deprecated. Use `require 'cucumber_factory/add_steps'` instead." 26 | 27 | add_steps_filepath = File.join(File.dirname(__FILE__), 'cucumber_factory/add_steps.rb') 28 | main.instance_eval(File.read(add_steps_filepath)) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), "/../../lib" ) 2 | 3 | require 'logger' 4 | require 'cucumber_factory' 5 | require 'gemika' 6 | require 'factory_bot' 7 | require 'carrierwave' 8 | require 'carrierwave/orm/activerecord' 9 | 10 | if ActiveRecord.respond_to?(:default_timezone=) 11 | ActiveRecord.default_timezone = :local 12 | else 13 | # Legacy method that was removed in Rails 7.1: 14 | ActiveRecord::Base.default_timezone = :local 15 | end 16 | 17 | Dir["#{File.dirname(__FILE__)}/support/uploaders/*.rb"].sort.each {|f| require f} 18 | Dir["#{File.dirname(__FILE__)}/support/models/*.rb"].sort.each {|f| require f} 19 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each {|f| require f} 20 | Dir["#{File.dirname(__FILE__)}/shared_examples/**/*.rb"].sort.each {|f| require f} 21 | 22 | Gemika::RSpec.configure_clean_database_before_example 23 | 24 | Gemika::RSpec.configure_should_syntax 25 | 26 | Gemika::RSpec.configure do |config| 27 | config.before(:each) do 28 | PlainRubyClass.reset 29 | end 30 | config.include FactoryBot::Syntax::Methods 31 | end 32 | -------------------------------------------------------------------------------- /spec/support/database.rb: -------------------------------------------------------------------------------- 1 | Gemika::Database.new.rewrite_schema! do 2 | 3 | create_table :movies do |t| 4 | t.string :title 5 | t.integer :year 6 | t.integer :prequel_id 7 | t.integer :reviewer_id 8 | t.string :uuid_reviewer_id 9 | t.integer :box_office_result 10 | t.string :premiere_site_type 11 | t.bigint :premiere_site_id 12 | end 13 | 14 | create_table :users do |t| 15 | t.string :email 16 | t.string :name 17 | t.boolean :deleted 18 | t.boolean :locked 19 | t.boolean :subscribed 20 | t.boolean :scared 21 | t.boolean :scared_by_spiders 22 | t.datetime :created_at 23 | end 24 | 25 | create_table :uuid_users, :id => false do |t| 26 | t.string :id 27 | t.string :email 28 | t.string :name 29 | t.datetime :created_at 30 | end 31 | 32 | create_table :payments do |t| 33 | t.text :comment 34 | t.integer :amount 35 | t.string :attachment 36 | end 37 | 38 | create_table :actors do |t| 39 | end 40 | 41 | create_table :job_offers do |t| 42 | end 43 | 44 | create_table :operas do |t| 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2016 Henning Koch 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /spec/support/factories/factories.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :job_offer, :class => ::JobOffer do 3 | transient do 4 | my_transient_attribute { nil } 5 | end 6 | 7 | trait :tempting_job_offer do 8 | transient do 9 | other_transient_attribute { nil } 10 | end 11 | end 12 | trait :risky 13 | trait :lucrative 14 | end 15 | 16 | factory :user, :class => User do 17 | transient do 18 | movie { nil } 19 | film { nil } 20 | end 21 | 22 | after(:build) do |user, evaluator| 23 | if user.reviewed_movies.blank? && (evaluator.movie || evaluator.film) 24 | user.reviewed_movies << (evaluator.movie || evaluator.film) 25 | end 26 | end 27 | end 28 | 29 | factory :movie, :class => Movie do 30 | transient do 31 | user { nil } 32 | user_id { nil } 33 | end 34 | 35 | after(:build) do |movie, evaluator| 36 | movie.reviewer = evaluator.user if evaluator.user 37 | movie.reviewer_id = evaluator.user_id if evaluator.user_id 38 | end 39 | 40 | trait :parent_movie_trait 41 | 42 | factory :subgenre_movie, traits: [:parent_movie_trait] 43 | end 44 | factory :opera, :class => Opera 45 | factory :payment, :class => Payment 46 | factory :uuid_user, :class => UuidUser 47 | factory :film, :class => Movie 48 | end 49 | -------------------------------------------------------------------------------- /lib/cucumber_factory/switcher.rb: -------------------------------------------------------------------------------- 1 | module CucumberFactory 2 | module Switcher 3 | extend self 4 | 5 | def find_last(klass) 6 | # Don't use class.last, in sqlite that is not always the last inserted element 7 | # If created_at is available prefer it over id as column for ordering so that we can handle UUIDs 8 | primary_key = klass.primary_key 9 | has_numeric_primary_key = klass.columns_hash[primary_key].type == :integer 10 | order_column = if has_numeric_primary_key || !klass.column_names.include?('created_at') 11 | primary_key 12 | else 13 | "created_at, #{primary_key}" 14 | end 15 | if active_record_version < 4 16 | klass.find(:last, :order => order_column) 17 | else 18 | klass.order(order_column).last 19 | end 20 | end 21 | 22 | def assign_attributes(model, attributes) 23 | if active_record_version < 3 24 | model.send(:attributes=, attributes, false) # ignore attr_accessible 25 | elsif active_record_version < 4 26 | model.send(:assign_attributes, attributes, :without_protection => true) 27 | else 28 | model.send(:assign_attributes, attributes) 29 | end 30 | end 31 | 32 | private 33 | 34 | def active_record_version 35 | ActiveRecord::VERSION::MAJOR 36 | end 37 | 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /cucumber_factory.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "cucumber_factory/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = %q{cucumber_factory} 7 | spec.version = CucumberFactory::VERSION 8 | spec.authors = ["Henning Koch"] 9 | spec.email = %q{github@makandra.de} 10 | spec.homepage = %q{https://github.com/makandra/cucumber_factory} 11 | spec.summary = %q{Create records from Cucumber features without writing step definition.} 12 | spec.description = %q{Cucumber Factory allows you to create ActiveRecord models from your Cucumber features without writing step definitions for each model.} 13 | spec.license = 'MIT' 14 | spec.metadata = { 15 | 'source_code_uri' => 'https://github.com/makandra/cucumber_factory', 16 | 'bug_tracker_uri' => 'https://github.com/makandra/cucumber_factory/issues', 17 | 'changelog_uri' => 'https://github.com/makandra/cucumber_factory/blob/master/CHANGELOG.md', 18 | 'rubygems_mfa_required' => 'true', 19 | } 20 | 21 | spec.files = `git ls-files`.split("\n") 22 | spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 23 | spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 24 | spec.require_paths = ["lib"] 25 | 26 | spec.add_dependency('cucumber') 27 | spec.add_dependency('activesupport') 28 | spec.add_dependency('activerecord') 29 | spec.add_dependency('cucumber_priority', '>=1.1.0') 30 | 31 | end 32 | -------------------------------------------------------------------------------- /spec/support/cucumber_helper.rb: -------------------------------------------------------------------------------- 1 | def prepare_cucumber_example 2 | if Gem::Version.new(Cucumber::VERSION) >= Gem::Version.new('3.0') 3 | @runtime = Cucumber::Runtime.new 4 | scenario = double('scenario', :language => 'en', :accept_hook? => true) 5 | @runtime.send(:begin_scenario, scenario) 6 | @main = Object.new 7 | @main.extend(Cucumber::Glue::Dsl) 8 | else 9 | @runtime = Cucumber::Runtime.new 10 | language = support_code.ruby if support_code.respond_to?(:ruby) 11 | language ||= support_code.load_programming_language('rb') 12 | language 13 | scenario = double('scenario', :language => 'en', :accept_hook? => true) 14 | language.send(:begin_scenario, scenario) 15 | @world = language.current_world 16 | @main = Object.new 17 | @main.extend(Cucumber::RbSupport::RbDsl) 18 | end 19 | 20 | add_steps_filepath = File.expand_path(File.join(File.dirname(__FILE__), '../../lib/cucumber_factory/add_steps.rb')) 21 | @main.instance_eval(File.read(add_steps_filepath)) 22 | end 23 | 24 | def invoke_cucumber_step(step, doc_string = nil, data_table = nil) 25 | if Gem::Version.new(Cucumber::VERSION) >= Gem::Version.new('2.0') 26 | multiline_argument = Cucumber::MultilineArgument::None.new 27 | 28 | if doc_string.present? 29 | multiline_argument = Cucumber::MultilineArgument::DocString.new(doc_string) 30 | end 31 | 32 | if data_table.present? 33 | multiline_argument = Cucumber::MultilineArgument::DataTable.from(data_table) 34 | end 35 | else 36 | multiline_argument = nil 37 | 38 | if doc_string.present? 39 | multiline_argument = Cucumber::Ast::DocString.new(doc_string, '') 40 | end 41 | 42 | if data_table.present? 43 | multiline_argument = Cucumber::Ast::Table.parse(data_table, nil, nil) 44 | end 45 | end 46 | 47 | first_step_match(step).invoke(multiline_argument) 48 | end 49 | 50 | def support_code 51 | @runtime.instance_variable_get(:@support_code) 52 | end 53 | 54 | def first_step_match(*args) 55 | support_code.send(:step_matches, *args).first 56 | end 57 | -------------------------------------------------------------------------------- /spec/cucumber_factory/factory/build_strategy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe CucumberFactory::BuildStrategy do 4 | 5 | # most of the behaviour is integration tested in steps_spec.rb 6 | 7 | describe '.from_prose' do 8 | 9 | context 'when describing a factory_bot factory' do 10 | it 'returns a strategy and transient attributes corresponding to the factories model' do 11 | strategy, transient_attributes = described_class.from_prose('job offer', nil) 12 | 13 | strategy.should be_a(described_class) 14 | strategy.model_class.should == JobOffer 15 | transient_attributes.should == [:my_transient_attribute] 16 | end 17 | 18 | it 'uses the variant for the factory name if present' do 19 | strategy, transient_attributes = described_class.from_prose('job offer', '(tempting_job_offer)') 20 | 21 | strategy.should be_a(described_class) 22 | strategy.model_class.should == JobOffer 23 | transient_attributes.should == [:my_transient_attribute, :other_transient_attribute] 24 | end 25 | end 26 | 27 | context 'when describing a non factory_bot model' do 28 | before do 29 | hide_const("FactoryBot") 30 | end 31 | 32 | it "should return a strategy for the class matching a natural language expression" do 33 | described_class.from_prose("movie", nil).first.model_class.should == Movie 34 | described_class.from_prose("job offer", nil).first.model_class.should == JobOffer 35 | end 36 | 37 | it "should ignore variants for the class name" do 38 | described_class.from_prose("movie", "(job offer)").first.model_class.should == Movie 39 | end 40 | 41 | it "should allow namespaced models" do 42 | described_class.from_prose("people/actor", nil).first.model_class.should == People::Actor 43 | end 44 | end 45 | 46 | end 47 | 48 | describe '.class_from_factory' do 49 | it 'returns the class associated with a factory_bot factory' do 50 | described_class.class_from_factory('film').should == Movie 51 | end 52 | end 53 | 54 | end 55 | -------------------------------------------------------------------------------- /Gemfile.cucumber-1.3.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 0.2.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (4.2.11.1) 14 | activesupport (= 4.2.11.1) 15 | builder (~> 3.1) 16 | activerecord (4.2.11.1) 17 | activemodel (= 4.2.11.1) 18 | activesupport (= 4.2.11.1) 19 | arel (~> 6.0) 20 | activesupport (4.2.11.1) 21 | i18n (~> 0.7) 22 | minitest (~> 5.1) 23 | thread_safe (~> 0.3, >= 0.3.4) 24 | tzinfo (~> 1.1) 25 | arel (6.0.4) 26 | builder (3.2.4) 27 | carrierwave (1.2.3) 28 | activemodel (>= 4.0.0) 29 | activesupport (>= 4.0.0) 30 | mime-types (>= 1.16) 31 | concurrent-ruby (1.1.6) 32 | cucumber (1.3.20) 33 | builder (>= 2.1.2) 34 | diff-lcs (>= 1.1.3) 35 | gherkin (~> 2.12) 36 | multi_json (>= 1.7.5, < 2.0) 37 | multi_test (>= 0.1.2) 38 | cucumber_priority (0.3.2) 39 | cucumber 40 | database_cleaner (1.0.1) 41 | diff-lcs (1.3) 42 | factory_bot (4.11.1) 43 | activesupport (>= 3.0.0) 44 | gemika (0.8.3) 45 | gherkin (2.12.2) 46 | multi_json (~> 1.3) 47 | i18n (0.9.5) 48 | concurrent-ruby (~> 1.0) 49 | mime-types (3.2.2) 50 | mime-types-data (~> 3.2015) 51 | mime-types-data (3.2018.0812) 52 | minitest (5.12.0) 53 | multi_json (1.14.1) 54 | multi_test (0.1.2) 55 | pg (0.21.0) 56 | rake (12.3.3) 57 | rspec (3.9.0) 58 | rspec-core (~> 3.9.0) 59 | rspec-expectations (~> 3.9.0) 60 | rspec-mocks (~> 3.9.0) 61 | rspec-core (3.9.1) 62 | rspec-support (~> 3.9.1) 63 | rspec-expectations (3.9.0) 64 | diff-lcs (>= 1.2.0, < 2.0) 65 | rspec-support (~> 3.9.0) 66 | rspec-mocks (3.9.1) 67 | diff-lcs (>= 1.2.0, < 2.0) 68 | rspec-support (~> 3.9.0) 69 | rspec-support (3.9.2) 70 | thread_safe (0.3.6) 71 | tzinfo (1.2.6) 72 | thread_safe (~> 0.1) 73 | 74 | PLATFORMS 75 | ruby 76 | 77 | DEPENDENCIES 78 | activerecord (~> 4.2.0) 79 | activesupport (~> 4.2.0) 80 | carrierwave (~> 1.0) 81 | cucumber (~> 1.3.20) 82 | cucumber_factory! 83 | database_cleaner (~> 1.0.0) 84 | factory_bot (< 5) 85 | gemika (>= 0.8.1) 86 | pg (< 1) 87 | rake (>= 10.0.4) 88 | rspec (> 3.0) 89 | 90 | BUNDLED WITH 91 | 1.17.3 92 | -------------------------------------------------------------------------------- /Gemfile.cucumber-2.4.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 0.2.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (4.2.10) 14 | activesupport (= 4.2.10) 15 | builder (~> 3.1) 16 | activerecord (4.2.10) 17 | activemodel (= 4.2.10) 18 | activesupport (= 4.2.10) 19 | arel (~> 6.0) 20 | activesupport (4.2.10) 21 | i18n (~> 0.7) 22 | minitest (~> 5.1) 23 | thread_safe (~> 0.3, >= 0.3.4) 24 | tzinfo (~> 1.1) 25 | arel (6.0.4) 26 | builder (3.2.3) 27 | carrierwave (1.2.3) 28 | activemodel (>= 4.0.0) 29 | activesupport (>= 4.0.0) 30 | mime-types (>= 1.16) 31 | concurrent-ruby (1.0.5) 32 | cucumber (2.4.0) 33 | builder (>= 2.1.2) 34 | cucumber-core (~> 1.5.0) 35 | cucumber-wire (~> 0.0.1) 36 | diff-lcs (>= 1.1.3) 37 | gherkin (~> 4.0) 38 | multi_json (>= 1.7.5, < 2.0) 39 | multi_test (>= 0.1.2) 40 | cucumber-core (1.5.0) 41 | gherkin (~> 4.0) 42 | cucumber-wire (0.0.1) 43 | cucumber_priority (0.3.2) 44 | cucumber 45 | database_cleaner (1.6.2) 46 | diff-lcs (1.3) 47 | factory_bot (4.11.1) 48 | activesupport (>= 3.0.0) 49 | gemika (0.8.2) 50 | gherkin (4.1.3) 51 | i18n (0.9.5) 52 | concurrent-ruby (~> 1.0) 53 | mime-types (3.2.2) 54 | mime-types-data (~> 3.2015) 55 | mime-types-data (3.2018.0812) 56 | minitest (5.11.3) 57 | multi_json (1.13.1) 58 | multi_test (0.1.2) 59 | pg (0.21.0) 60 | rake (12.3.0) 61 | rspec (3.7.0) 62 | rspec-core (~> 3.7.0) 63 | rspec-expectations (~> 3.7.0) 64 | rspec-mocks (~> 3.7.0) 65 | rspec-core (3.7.1) 66 | rspec-support (~> 3.7.0) 67 | rspec-expectations (3.7.0) 68 | diff-lcs (>= 1.2.0, < 2.0) 69 | rspec-support (~> 3.7.0) 70 | rspec-mocks (3.7.0) 71 | diff-lcs (>= 1.2.0, < 2.0) 72 | rspec-support (~> 3.7.0) 73 | rspec-support (3.7.1) 74 | thread_safe (0.3.6) 75 | tzinfo (1.2.5) 76 | thread_safe (~> 0.1) 77 | 78 | PLATFORMS 79 | ruby 80 | 81 | DEPENDENCIES 82 | activerecord (~> 4.2.0) 83 | activesupport (~> 4.2.0) 84 | carrierwave (~> 1.0) 85 | cucumber (~> 2.4.0) 86 | cucumber_factory! 87 | database_cleaner 88 | factory_bot (< 5) 89 | gemika (>= 0.8.1) 90 | pg (< 1) 91 | rake 92 | rspec (~> 3.0) 93 | 94 | BUNDLED WITH 95 | 1.17.3 96 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tests 3 | 'on': 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | test: 12 | runs-on: ubuntu-22.04 13 | services: 14 | postgres: 15 | image: postgres:14.15 16 | env: 17 | POSTGRES_PASSWORD: postgres 18 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 19 | ports: 20 | - 5432:5432 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | include: 25 | - ruby: 2.5.3 26 | gemfile: Gemfile.cucumber-1.3 27 | bundler: 1.17.3 28 | - ruby: 2.5.3 29 | gemfile: Gemfile.cucumber-2.4 30 | bundler: 1.17.3 31 | - ruby: 2.5.3 32 | gemfile: Gemfile.cucumber-3.0 33 | bundler: 1.17.3 34 | - ruby: 2.5.3 35 | gemfile: Gemfile.cucumber-3.1 36 | bundler: 1.17.3 37 | 38 | - ruby: 2.6.6 39 | gemfile: Gemfile.cucumber-4.1 40 | bundler: 2.4.22 41 | 42 | - ruby: 2.7.2 43 | gemfile: Gemfile.cucumber-5.3 44 | bundler: 2.4.22 45 | 46 | - ruby: 3.2.0 47 | gemfile: Gemfile.cucumber-4.1 48 | bundler: 2.4.22 49 | - ruby: 3.2.0 50 | gemfile: Gemfile.cucumber-5.3 51 | bundler: 2.4.22 52 | - ruby: 3.2.0 53 | gemfile: Gemfile.rails-7 54 | bundler: 2.6.3 55 | - ruby: 3.2.0 56 | gemfile: Gemfile.rails-8 57 | bundler: 2.6.3 58 | - ruby: 3.4.1 59 | gemfile: Gemfile.rails-7 60 | bundler: 2.6.3 61 | - ruby: 3.4.1 62 | gemfile: Gemfile.rails-8 63 | bundler: 2.6.3 64 | 65 | - ruby: 3.4.1 66 | gemfile: Gemfile.cucumber-10.1 67 | bundler: 2.6.3 68 | 69 | env: 70 | BUNDLE_GEMFILE: "${{ matrix.gemfile }}" 71 | steps: 72 | - uses: actions/checkout@v2 73 | - name: Install ruby 74 | uses: ruby/setup-ruby@v1 75 | with: 76 | ruby-version: "${{ matrix.ruby }}" 77 | - name: Setup database 78 | run: | 79 | sudo apt-get install -y postgresql-client 80 | PGPASSWORD=postgres psql -c 'create database cucumber_factory_test;' -U postgres -p 5432 -h localhost 81 | - name: Bundle 82 | run: | 83 | gem install bundler:${{ matrix.bundler }} 84 | bundle _${{ matrix.bundler }}_ install --no-deployment 85 | - name: Run tests 86 | run: bundle _${{ matrix.bundler }}_ exec rspec 87 | -------------------------------------------------------------------------------- /Gemfile.cucumber-3.1.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 0.2.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (5.1.5) 14 | activesupport (= 5.1.5) 15 | activerecord (5.1.5) 16 | activemodel (= 5.1.5) 17 | activesupport (= 5.1.5) 18 | arel (~> 8.0) 19 | activesupport (5.1.5) 20 | concurrent-ruby (~> 1.0, >= 1.0.2) 21 | i18n (~> 0.7) 22 | minitest (~> 5.1) 23 | tzinfo (~> 1.1) 24 | arel (8.0.0) 25 | backports (3.11.1) 26 | builder (3.2.3) 27 | carrierwave (1.2.3) 28 | activemodel (>= 4.0.0) 29 | activesupport (>= 4.0.0) 30 | mime-types (>= 1.16) 31 | concurrent-ruby (1.0.5) 32 | cucumber (3.1.0) 33 | builder (>= 2.1.2) 34 | cucumber-core (~> 3.1.0) 35 | cucumber-expressions (~> 5.0.4) 36 | cucumber-wire (~> 0.0.1) 37 | diff-lcs (~> 1.3) 38 | gherkin (~> 5.0) 39 | multi_json (>= 1.7.5, < 2.0) 40 | multi_test (>= 0.1.2) 41 | cucumber-core (3.1.0) 42 | backports (>= 3.8.0) 43 | cucumber-tag_expressions (~> 1.1.0) 44 | gherkin (>= 5.0.0) 45 | cucumber-expressions (5.0.13) 46 | cucumber-tag_expressions (1.1.1) 47 | cucumber-wire (0.0.1) 48 | cucumber_priority (0.3.2) 49 | cucumber 50 | database_cleaner (1.6.2) 51 | diff-lcs (1.3) 52 | factory_bot (5.1.1) 53 | activesupport (>= 4.2.0) 54 | gemika (0.8.3) 55 | gherkin (5.0.0) 56 | i18n (0.9.5) 57 | concurrent-ruby (~> 1.0) 58 | mime-types (3.2.2) 59 | mime-types-data (~> 3.2015) 60 | mime-types-data (3.2018.0812) 61 | minitest (5.11.3) 62 | multi_json (1.13.1) 63 | multi_test (0.1.2) 64 | pg (1.5.4) 65 | rake (12.3.0) 66 | rspec (3.7.0) 67 | rspec-core (~> 3.7.0) 68 | rspec-expectations (~> 3.7.0) 69 | rspec-mocks (~> 3.7.0) 70 | rspec-core (3.7.1) 71 | rspec-support (~> 3.7.0) 72 | rspec-expectations (3.7.0) 73 | diff-lcs (>= 1.2.0, < 2.0) 74 | rspec-support (~> 3.7.0) 75 | rspec-mocks (3.7.0) 76 | diff-lcs (>= 1.2.0, < 2.0) 77 | rspec-support (~> 3.7.0) 78 | rspec-support (3.7.1) 79 | thread_safe (0.3.6) 80 | tzinfo (1.2.5) 81 | thread_safe (~> 0.1) 82 | 83 | PLATFORMS 84 | ruby 85 | 86 | DEPENDENCIES 87 | activerecord (~> 5.1.0) 88 | activesupport (~> 5.1.0) 89 | carrierwave 90 | cucumber (~> 3.1.0) 91 | cucumber_factory! 92 | database_cleaner 93 | factory_bot 94 | gemika (>= 0.8.1) 95 | pg 96 | rake 97 | rspec (~> 3.0) 98 | 99 | BUNDLED WITH 100 | 1.17.3 101 | -------------------------------------------------------------------------------- /Gemfile.cucumber-3.0.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 0.2.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (5.0.6) 14 | activesupport (= 5.0.6) 15 | activerecord (5.0.6) 16 | activemodel (= 5.0.6) 17 | activesupport (= 5.0.6) 18 | arel (~> 7.0) 19 | activesupport (5.0.6) 20 | concurrent-ruby (~> 1.0, >= 1.0.2) 21 | i18n (~> 0.7) 22 | minitest (~> 5.1) 23 | tzinfo (~> 1.1) 24 | arel (7.1.4) 25 | backports (3.11.1) 26 | builder (3.2.3) 27 | carrierwave (1.2.3) 28 | activemodel (>= 4.0.0) 29 | activesupport (>= 4.0.0) 30 | mime-types (>= 1.16) 31 | concurrent-ruby (1.0.5) 32 | cucumber (3.0.2) 33 | builder (>= 2.1.2) 34 | cucumber-core (~> 3.0.0) 35 | cucumber-expressions (~> 4.0.3) 36 | cucumber-wire (~> 0.0.1) 37 | diff-lcs (~> 1.3) 38 | gherkin (~> 4.0) 39 | multi_json (>= 1.7.5, < 2.0) 40 | multi_test (>= 0.1.2) 41 | cucumber-core (3.0.0) 42 | backports (>= 3.8.0) 43 | cucumber-tag_expressions (>= 1.0.1) 44 | gherkin (>= 4.1.3) 45 | cucumber-expressions (4.0.4) 46 | cucumber-tag_expressions (1.1.1) 47 | cucumber-wire (0.0.1) 48 | cucumber_priority (0.3.2) 49 | cucumber 50 | database_cleaner (1.6.2) 51 | diff-lcs (1.3) 52 | factory_bot (5.1.1) 53 | activesupport (>= 4.2.0) 54 | gemika (0.8.3) 55 | gherkin (4.1.3) 56 | i18n (0.9.5) 57 | concurrent-ruby (~> 1.0) 58 | mime-types (3.2.2) 59 | mime-types-data (~> 3.2015) 60 | mime-types-data (3.2018.0812) 61 | minitest (5.11.3) 62 | multi_json (1.13.1) 63 | multi_test (0.1.2) 64 | pg (0.21.0) 65 | rake (12.3.0) 66 | rspec (3.7.0) 67 | rspec-core (~> 3.7.0) 68 | rspec-expectations (~> 3.7.0) 69 | rspec-mocks (~> 3.7.0) 70 | rspec-core (3.7.1) 71 | rspec-support (~> 3.7.0) 72 | rspec-expectations (3.7.0) 73 | diff-lcs (>= 1.2.0, < 2.0) 74 | rspec-support (~> 3.7.0) 75 | rspec-mocks (3.7.0) 76 | diff-lcs (>= 1.2.0, < 2.0) 77 | rspec-support (~> 3.7.0) 78 | rspec-support (3.7.1) 79 | thread_safe (0.3.6) 80 | tzinfo (1.2.5) 81 | thread_safe (~> 0.1) 82 | 83 | PLATFORMS 84 | ruby 85 | 86 | DEPENDENCIES 87 | activerecord (~> 5.0.0) 88 | activesupport (~> 5.0.0) 89 | carrierwave 90 | cucumber (~> 3.0.0) 91 | cucumber_factory! 92 | database_cleaner 93 | factory_bot 94 | gemika (>= 0.8.1) 95 | pg (< 1) 96 | rake 97 | rspec (~> 3.0) 98 | 99 | BUNDLED WITH 100 | 1.17.3 101 | -------------------------------------------------------------------------------- /Gemfile.rails-7.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 1.1.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (7.0.8.7) 14 | activesupport (= 7.0.8.7) 15 | activerecord (7.0.8.7) 16 | activemodel (= 7.0.8.7) 17 | activesupport (= 7.0.8.7) 18 | activesupport (7.0.8.7) 19 | concurrent-ruby (~> 1.0, >= 1.0.2) 20 | i18n (>= 1.6, < 2) 21 | minitest (>= 5.1) 22 | tzinfo (~> 2.0) 23 | addressable (2.8.7) 24 | public_suffix (>= 2.0.2, < 7.0) 25 | base64 (0.2.0) 26 | bigdecimal (3.1.9) 27 | builder (3.3.0) 28 | carrierwave (3.1.1) 29 | activemodel (>= 6.0.0) 30 | activesupport (>= 6.0.0) 31 | addressable (~> 2.6) 32 | image_processing (~> 1.1) 33 | marcel (~> 1.0.0) 34 | ssrf_filter (~> 1.0) 35 | concurrent-ruby (1.3.5) 36 | cucumber (9.2.1) 37 | builder (~> 3.2) 38 | cucumber-ci-environment (> 9, < 11) 39 | cucumber-core (> 13, < 14) 40 | cucumber-cucumber-expressions (~> 17.0) 41 | cucumber-gherkin (> 24, < 28) 42 | cucumber-html-formatter (> 20.3, < 22) 43 | cucumber-messages (> 19, < 25) 44 | diff-lcs (~> 1.5) 45 | mini_mime (~> 1.1) 46 | multi_test (~> 1.1) 47 | sys-uname (~> 1.2) 48 | cucumber-ci-environment (10.0.1) 49 | cucumber-core (13.0.3) 50 | cucumber-gherkin (>= 27, < 28) 51 | cucumber-messages (>= 20, < 23) 52 | cucumber-tag-expressions (> 5, < 7) 53 | cucumber-cucumber-expressions (17.1.0) 54 | bigdecimal 55 | cucumber-gherkin (27.0.0) 56 | cucumber-messages (>= 19.1.4, < 23) 57 | cucumber-html-formatter (21.7.0) 58 | cucumber-messages (> 19, < 27) 59 | cucumber-messages (22.0.0) 60 | cucumber-tag-expressions (6.1.1) 61 | cucumber_priority (1.1.0) 62 | cucumber 63 | database_cleaner (2.1.0) 64 | database_cleaner-active_record (>= 2, < 3) 65 | database_cleaner-active_record (2.2.0) 66 | activerecord (>= 5.a) 67 | database_cleaner-core (~> 2.0.0) 68 | database_cleaner-core (2.0.1) 69 | diff-lcs (1.5.1) 70 | factory_bot (6.5.0) 71 | activesupport (>= 5.0.0) 72 | ffi (1.17.1-x86_64-linux-gnu) 73 | gemika (0.8.4) 74 | i18n (1.14.7) 75 | concurrent-ruby (~> 1.0) 76 | image_processing (1.13.0) 77 | mini_magick (>= 4.9.5, < 5) 78 | ruby-vips (>= 2.0.17, < 3) 79 | logger (1.6.5) 80 | marcel (1.0.4) 81 | mini_magick (4.13.2) 82 | mini_mime (1.1.5) 83 | minitest (5.25.4) 84 | multi_test (1.1.0) 85 | mutex_m (0.3.0) 86 | pg (1.5.9) 87 | public_suffix (6.0.1) 88 | rake (13.2.1) 89 | rspec (3.13.0) 90 | rspec-core (~> 3.13.0) 91 | rspec-expectations (~> 3.13.0) 92 | rspec-mocks (~> 3.13.0) 93 | rspec-core (3.13.2) 94 | rspec-support (~> 3.13.0) 95 | rspec-expectations (3.13.3) 96 | diff-lcs (>= 1.2.0, < 2.0) 97 | rspec-support (~> 3.13.0) 98 | rspec-mocks (3.13.2) 99 | diff-lcs (>= 1.2.0, < 2.0) 100 | rspec-support (~> 3.13.0) 101 | rspec-support (3.13.2) 102 | ruby-vips (2.2.2) 103 | ffi (~> 1.12) 104 | logger 105 | ssrf_filter (1.2.0) 106 | sys-uname (1.3.1) 107 | ffi (~> 1.1) 108 | tzinfo (2.0.6) 109 | concurrent-ruby (~> 1.0) 110 | 111 | PLATFORMS 112 | x86_64-linux 113 | 114 | DEPENDENCIES 115 | activerecord (~> 7.0.1) 116 | activesupport (~> 7.0.1) 117 | base64 118 | bigdecimal 119 | carrierwave 120 | cucumber (~> 9.2.1) 121 | cucumber_factory! 122 | database_cleaner 123 | factory_bot 124 | gemika (>= 0.8.1) 125 | logger 126 | mutex_m 127 | pg (> 1.2.3) 128 | rake (> 10.0) 129 | rspec (~> 3.0) 130 | 131 | BUNDLED WITH 132 | 2.6.3 133 | -------------------------------------------------------------------------------- /media/logo.light.text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 28 | 32 | 36 | 37 | 47 | 48 | 71 | 73 | 74 | 76 | image/svg+xml 77 | 79 | 80 | 81 | 82 | 83 | 88 | CUCUMBER_FACTORY 99 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /media/logo.dark.text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 28 | 32 | 36 | 37 | 47 | 48 | 74 | 76 | 77 | 79 | image/svg+xml 80 | 82 | 83 | 84 | 85 | 86 | 91 | CUCUMBER_FACTORY 102 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Gemfile.cucumber-5.3.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 1.1.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (6.1.3.1) 14 | activesupport (= 6.1.3.1) 15 | activerecord (6.1.3.1) 16 | activemodel (= 6.1.3.1) 17 | activesupport (= 6.1.3.1) 18 | activesupport (6.1.3.1) 19 | concurrent-ruby (~> 1.0, >= 1.0.2) 20 | i18n (>= 1.6, < 2) 21 | minitest (>= 5.1) 22 | tzinfo (~> 2.0) 23 | zeitwerk (~> 2.3) 24 | addressable (2.7.0) 25 | public_suffix (>= 2.0.2, < 5.0) 26 | builder (3.2.4) 27 | carrierwave (2.2.1) 28 | activemodel (>= 5.0.0) 29 | activesupport (>= 5.0.0) 30 | addressable (~> 2.6) 31 | image_processing (~> 1.1) 32 | marcel (~> 1.0.0) 33 | mini_mime (>= 0.1.3) 34 | ssrf_filter (~> 1.0) 35 | concurrent-ruby (1.1.8) 36 | cucumber (5.3.0) 37 | builder (~> 3.2, >= 3.2.4) 38 | cucumber-core (~> 8.0, >= 8.0.1) 39 | cucumber-create-meta (~> 2.0, >= 2.0.2) 40 | cucumber-cucumber-expressions (~> 10.3, >= 10.3.0) 41 | cucumber-gherkin (~> 15.0, >= 15.0.2) 42 | cucumber-html-formatter (~> 9.0, >= 9.0.0) 43 | cucumber-messages (~> 13.1, >= 13.1.0) 44 | cucumber-wire (~> 4.0, >= 4.0.1) 45 | diff-lcs (~> 1.4, >= 1.4.4) 46 | multi_test (~> 0.1, >= 0.1.2) 47 | sys-uname (~> 1.2, >= 1.2.1) 48 | cucumber-core (8.0.1) 49 | cucumber-gherkin (~> 15.0, >= 15.0.2) 50 | cucumber-messages (~> 13.0, >= 13.0.1) 51 | cucumber-tag-expressions (~> 2.0, >= 2.0.4) 52 | cucumber-create-meta (2.0.4) 53 | cucumber-messages (~> 13.1, >= 13.1.0) 54 | sys-uname (~> 1.2, >= 1.2.1) 55 | cucumber-cucumber-expressions (10.3.0) 56 | cucumber-gherkin (15.0.2) 57 | cucumber-messages (~> 13.0, >= 13.0.1) 58 | cucumber-html-formatter (9.0.0) 59 | cucumber-messages (~> 13.0, >= 13.0.1) 60 | cucumber-messages (13.2.1) 61 | protobuf-cucumber (~> 3.10, >= 3.10.8) 62 | cucumber-tag-expressions (2.0.4) 63 | cucumber-wire (4.0.1) 64 | cucumber-core (~> 8.0, >= 8.0.1) 65 | cucumber-cucumber-expressions (~> 10.3, >= 10.3.0) 66 | cucumber-messages (~> 13.0, >= 13.0.1) 67 | cucumber_priority (1.1.0) 68 | cucumber 69 | database_cleaner (2.0.1) 70 | database_cleaner-active_record (~> 2.0.0) 71 | database_cleaner-active_record (2.0.0) 72 | activerecord (>= 5.a) 73 | database_cleaner-core (~> 2.0.0) 74 | database_cleaner-core (2.0.1) 75 | diff-lcs (1.4.4) 76 | factory_bot (6.1.0) 77 | activesupport (>= 5.0.0) 78 | ffi (1.15.0) 79 | gemika (0.8.3) 80 | i18n (1.8.9) 81 | concurrent-ruby (~> 1.0) 82 | image_processing (1.12.1) 83 | mini_magick (>= 4.9.5, < 5) 84 | ruby-vips (>= 2.0.17, < 3) 85 | marcel (1.0.0) 86 | middleware (0.1.0) 87 | mini_magick (4.11.0) 88 | mini_mime (1.0.3) 89 | minitest (5.14.4) 90 | multi_test (0.1.2) 91 | pg (1.5.4) 92 | protobuf-cucumber (3.10.8) 93 | activesupport (>= 3.2) 94 | middleware 95 | thor 96 | thread_safe 97 | public_suffix (4.0.6) 98 | rake (13.0.3) 99 | rspec (3.10.0) 100 | rspec-core (~> 3.10.0) 101 | rspec-expectations (~> 3.10.0) 102 | rspec-mocks (~> 3.10.0) 103 | rspec-core (3.10.1) 104 | rspec-support (~> 3.10.0) 105 | rspec-expectations (3.10.1) 106 | diff-lcs (>= 1.2.0, < 2.0) 107 | rspec-support (~> 3.10.0) 108 | rspec-mocks (3.10.2) 109 | diff-lcs (>= 1.2.0, < 2.0) 110 | rspec-support (~> 3.10.0) 111 | rspec-support (3.10.2) 112 | ruby-vips (2.1.0) 113 | ffi (~> 1.12) 114 | ssrf_filter (1.0.7) 115 | sys-uname (1.2.2) 116 | ffi (~> 1.1) 117 | thor (1.1.0) 118 | thread_safe (0.3.6) 119 | tzinfo (2.0.4) 120 | concurrent-ruby (~> 1.0) 121 | zeitwerk (2.4.2) 122 | 123 | PLATFORMS 124 | ruby 125 | 126 | DEPENDENCIES 127 | activerecord (~> 6.1.0) 128 | activesupport (~> 6.1.0) 129 | carrierwave 130 | cucumber (~> 5.3.0) 131 | cucumber_factory! 132 | database_cleaner 133 | factory_bot 134 | gemika (>= 0.8.1) 135 | pg (> 1.2.3) 136 | rake (> 10.0) 137 | rspec (~> 3.0) 138 | 139 | BUNDLED WITH 140 | 2.4.22 141 | -------------------------------------------------------------------------------- /Gemfile.cucumber-4.1.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 1.1.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (6.0.3.6) 14 | activesupport (= 6.0.3.6) 15 | activerecord (6.0.3.6) 16 | activemodel (= 6.0.3.6) 17 | activesupport (= 6.0.3.6) 18 | activesupport (6.0.3.6) 19 | concurrent-ruby (~> 1.0, >= 1.0.2) 20 | i18n (>= 0.7, < 2) 21 | minitest (~> 5.1) 22 | tzinfo (~> 1.1) 23 | zeitwerk (~> 2.2, >= 2.2.2) 24 | addressable (2.7.0) 25 | public_suffix (>= 2.0.2, < 5.0) 26 | builder (3.2.4) 27 | carrierwave (2.2.1) 28 | activemodel (>= 5.0.0) 29 | activesupport (>= 5.0.0) 30 | addressable (~> 2.6) 31 | image_processing (~> 1.1) 32 | marcel (~> 1.0.0) 33 | mini_mime (>= 0.1.3) 34 | ssrf_filter (~> 1.0) 35 | concurrent-ruby (1.1.8) 36 | cucumber (4.1.0) 37 | builder (~> 3.2, >= 3.2.3) 38 | cucumber-core (~> 7.1, >= 7.1.0) 39 | cucumber-create-meta (~> 1.0.0, >= 1.0.0) 40 | cucumber-cucumber-expressions (~> 10.1, >= 10.1.0) 41 | cucumber-gherkin (~> 14.0, >= 14.0.1) 42 | cucumber-html-formatter (~> 7.0, >= 7.0.0) 43 | cucumber-messages (~> 12.2, >= 12.2.0) 44 | cucumber-wire (~> 3.1, >= 3.1.0) 45 | diff-lcs (~> 1.3, >= 1.3, < 1.4) 46 | multi_test (~> 0.1, >= 0.1.2) 47 | sys-uname (~> 1.0, >= 1.0.2) 48 | cucumber-core (7.1.0) 49 | cucumber-gherkin (~> 14.0, >= 14.0.1) 50 | cucumber-messages (~> 12.2, >= 12.2.0) 51 | cucumber-tag-expressions (~> 2.0, >= 2.0.4) 52 | cucumber-create-meta (1.0.0) 53 | cucumber-messages (~> 12.2, >= 12.2.0) 54 | sys-uname (~> 1.2, >= 1.2.1) 55 | cucumber-cucumber-expressions (10.3.0) 56 | cucumber-gherkin (14.2.0) 57 | cucumber-messages (~> 12.4, >= 12.4.0) 58 | cucumber-html-formatter (7.2.0) 59 | cucumber-messages (~> 12.4, >= 12.4.0) 60 | cucumber-messages (12.4.0) 61 | protobuf-cucumber (~> 3.10, >= 3.10.8) 62 | cucumber-tag-expressions (2.0.4) 63 | cucumber-wire (3.1.0) 64 | cucumber-core (~> 7.1, >= 7.1.0) 65 | cucumber-cucumber-expressions (~> 10.1, >= 10.1.0) 66 | cucumber-messages (~> 12.2, >= 12.2.0) 67 | cucumber_priority (1.1.0) 68 | cucumber 69 | database_cleaner (2.0.1) 70 | database_cleaner-active_record (~> 2.0.0) 71 | database_cleaner-active_record (2.0.0) 72 | activerecord (>= 5.a) 73 | database_cleaner-core (~> 2.0.0) 74 | database_cleaner-core (2.0.1) 75 | diff-lcs (1.3) 76 | factory_bot (6.1.0) 77 | activesupport (>= 5.0.0) 78 | ffi (1.15.0) 79 | gemika (0.8.3) 80 | i18n (1.8.9) 81 | concurrent-ruby (~> 1.0) 82 | image_processing (1.12.1) 83 | mini_magick (>= 4.9.5, < 5) 84 | ruby-vips (>= 2.0.17, < 3) 85 | marcel (1.0.0) 86 | middleware (0.1.0) 87 | mini_magick (4.11.0) 88 | mini_mime (1.0.3) 89 | minitest (5.14.4) 90 | multi_test (0.1.2) 91 | pg (1.5.4) 92 | protobuf-cucumber (3.10.8) 93 | activesupport (>= 3.2) 94 | middleware 95 | thor 96 | thread_safe 97 | public_suffix (4.0.6) 98 | rake (13.0.3) 99 | rspec (3.10.0) 100 | rspec-core (~> 3.10.0) 101 | rspec-expectations (~> 3.10.0) 102 | rspec-mocks (~> 3.10.0) 103 | rspec-core (3.10.1) 104 | rspec-support (~> 3.10.0) 105 | rspec-expectations (3.10.1) 106 | diff-lcs (>= 1.2.0, < 2.0) 107 | rspec-support (~> 3.10.0) 108 | rspec-mocks (3.10.2) 109 | diff-lcs (>= 1.2.0, < 2.0) 110 | rspec-support (~> 3.10.0) 111 | rspec-support (3.10.2) 112 | ruby-vips (2.1.0) 113 | ffi (~> 1.12) 114 | ssrf_filter (1.0.7) 115 | sys-uname (1.2.2) 116 | ffi (~> 1.1) 117 | thor (1.1.0) 118 | thread_safe (0.3.6) 119 | tzinfo (1.2.9) 120 | thread_safe (~> 0.1) 121 | zeitwerk (2.4.2) 122 | 123 | PLATFORMS 124 | ruby 125 | 126 | DEPENDENCIES 127 | activerecord (~> 6.0.0) 128 | activesupport (~> 6.0.0) 129 | carrierwave 130 | cucumber (~> 4.1.0) 131 | cucumber_factory! 132 | database_cleaner 133 | factory_bot 134 | gemika (>= 0.8.1) 135 | pg (> 1.2.3) 136 | rake (> 10.0) 137 | rspec (~> 3.0) 138 | 139 | BUNDLED WITH 140 | 2.4.22 141 | -------------------------------------------------------------------------------- /Gemfile.rails-8.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 1.1.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (8.0.1) 14 | activesupport (= 8.0.1) 15 | activerecord (8.0.1) 16 | activemodel (= 8.0.1) 17 | activesupport (= 8.0.1) 18 | timeout (>= 0.4.0) 19 | activesupport (8.0.1) 20 | base64 21 | benchmark (>= 0.3) 22 | bigdecimal 23 | concurrent-ruby (~> 1.0, >= 1.3.1) 24 | connection_pool (>= 2.2.5) 25 | drb 26 | i18n (>= 1.6, < 2) 27 | logger (>= 1.4.2) 28 | minitest (>= 5.1) 29 | securerandom (>= 0.3) 30 | tzinfo (~> 2.0, >= 2.0.5) 31 | uri (>= 0.13.1) 32 | addressable (2.8.7) 33 | public_suffix (>= 2.0.2, < 7.0) 34 | base64 (0.2.0) 35 | benchmark (0.4.0) 36 | bigdecimal (3.1.9) 37 | builder (3.3.0) 38 | carrierwave (3.1.1) 39 | activemodel (>= 6.0.0) 40 | activesupport (>= 6.0.0) 41 | addressable (~> 2.6) 42 | image_processing (~> 1.1) 43 | marcel (~> 1.0.0) 44 | ssrf_filter (~> 1.0) 45 | concurrent-ruby (1.3.5) 46 | connection_pool (2.5.0) 47 | cucumber (9.2.1) 48 | builder (~> 3.2) 49 | cucumber-ci-environment (> 9, < 11) 50 | cucumber-core (> 13, < 14) 51 | cucumber-cucumber-expressions (~> 17.0) 52 | cucumber-gherkin (> 24, < 28) 53 | cucumber-html-formatter (> 20.3, < 22) 54 | cucumber-messages (> 19, < 25) 55 | diff-lcs (~> 1.5) 56 | mini_mime (~> 1.1) 57 | multi_test (~> 1.1) 58 | sys-uname (~> 1.2) 59 | cucumber-ci-environment (10.0.1) 60 | cucumber-core (13.0.3) 61 | cucumber-gherkin (>= 27, < 28) 62 | cucumber-messages (>= 20, < 23) 63 | cucumber-tag-expressions (> 5, < 7) 64 | cucumber-cucumber-expressions (17.1.0) 65 | bigdecimal 66 | cucumber-gherkin (27.0.0) 67 | cucumber-messages (>= 19.1.4, < 23) 68 | cucumber-html-formatter (21.7.0) 69 | cucumber-messages (> 19, < 27) 70 | cucumber-messages (22.0.0) 71 | cucumber-tag-expressions (6.1.1) 72 | cucumber_priority (1.1.0) 73 | cucumber 74 | database_cleaner (2.1.0) 75 | database_cleaner-active_record (>= 2, < 3) 76 | database_cleaner-active_record (2.2.0) 77 | activerecord (>= 5.a) 78 | database_cleaner-core (~> 2.0.0) 79 | database_cleaner-core (2.0.1) 80 | diff-lcs (1.5.1) 81 | drb (2.2.1) 82 | factory_bot (6.5.0) 83 | activesupport (>= 5.0.0) 84 | ffi (1.17.1) 85 | ffi (1.17.1-aarch64-linux-gnu) 86 | ffi (1.17.1-aarch64-linux-musl) 87 | ffi (1.17.1-arm-linux-gnu) 88 | ffi (1.17.1-arm-linux-musl) 89 | ffi (1.17.1-arm64-darwin) 90 | ffi (1.17.1-x86-linux-gnu) 91 | ffi (1.17.1-x86-linux-musl) 92 | ffi (1.17.1-x86_64-darwin) 93 | ffi (1.17.1-x86_64-linux-gnu) 94 | ffi (1.17.1-x86_64-linux-musl) 95 | gemika (0.8.4) 96 | i18n (1.14.7) 97 | concurrent-ruby (~> 1.0) 98 | image_processing (1.13.0) 99 | mini_magick (>= 4.9.5, < 5) 100 | ruby-vips (>= 2.0.17, < 3) 101 | logger (1.6.5) 102 | marcel (1.0.4) 103 | mini_magick (4.13.2) 104 | mini_mime (1.1.5) 105 | minitest (5.25.4) 106 | multi_test (1.1.0) 107 | pg (1.5.9) 108 | public_suffix (6.0.1) 109 | rake (13.2.1) 110 | rspec (3.13.0) 111 | rspec-core (~> 3.13.0) 112 | rspec-expectations (~> 3.13.0) 113 | rspec-mocks (~> 3.13.0) 114 | rspec-core (3.13.2) 115 | rspec-support (~> 3.13.0) 116 | rspec-expectations (3.13.3) 117 | diff-lcs (>= 1.2.0, < 2.0) 118 | rspec-support (~> 3.13.0) 119 | rspec-mocks (3.13.2) 120 | diff-lcs (>= 1.2.0, < 2.0) 121 | rspec-support (~> 3.13.0) 122 | rspec-support (3.13.2) 123 | ruby-vips (2.2.2) 124 | ffi (~> 1.12) 125 | logger 126 | securerandom (0.4.1) 127 | ssrf_filter (1.2.0) 128 | sys-uname (1.3.1) 129 | ffi (~> 1.1) 130 | timeout (0.4.3) 131 | tzinfo (2.0.6) 132 | concurrent-ruby (~> 1.0) 133 | uri (1.0.2) 134 | 135 | PLATFORMS 136 | aarch64-linux-gnu 137 | aarch64-linux-musl 138 | arm-linux-gnu 139 | arm-linux-musl 140 | arm64-darwin 141 | ruby 142 | x86-linux-gnu 143 | x86-linux-musl 144 | x86_64-darwin 145 | x86_64-linux-gnu 146 | x86_64-linux-musl 147 | 148 | DEPENDENCIES 149 | activerecord (~> 8.0.0) 150 | activesupport (~> 8.0.0) 151 | carrierwave 152 | cucumber (~> 9.2.1) 153 | cucumber_factory! 154 | database_cleaner 155 | factory_bot 156 | gemika (>= 0.8.1) 157 | pg (> 1.2.3) 158 | rake (> 10.0) 159 | rspec (~> 3.0) 160 | 161 | BUNDLED WITH 162 | 2.5.22 163 | -------------------------------------------------------------------------------- /Gemfile.cucumber-10.1.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cucumber_factory (2.7.0) 5 | activerecord 6 | activesupport 7 | cucumber 8 | cucumber_priority (>= 1.1.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | activemodel (8.0.2.1) 14 | activesupport (= 8.0.2.1) 15 | activerecord (8.0.2.1) 16 | activemodel (= 8.0.2.1) 17 | activesupport (= 8.0.2.1) 18 | timeout (>= 0.4.0) 19 | activesupport (8.0.2.1) 20 | base64 21 | benchmark (>= 0.3) 22 | bigdecimal 23 | concurrent-ruby (~> 1.0, >= 1.3.1) 24 | connection_pool (>= 2.2.5) 25 | drb 26 | i18n (>= 1.6, < 2) 27 | logger (>= 1.4.2) 28 | minitest (>= 5.1) 29 | securerandom (>= 0.3) 30 | tzinfo (~> 2.0, >= 2.0.5) 31 | uri (>= 0.13.1) 32 | addressable (2.8.7) 33 | public_suffix (>= 2.0.2, < 7.0) 34 | base64 (0.3.0) 35 | benchmark (0.4.1) 36 | bigdecimal (3.2.2) 37 | builder (3.3.0) 38 | carrierwave (3.1.2) 39 | activemodel (>= 6.0.0) 40 | activesupport (>= 6.0.0) 41 | addressable (~> 2.6) 42 | image_processing (~> 1.1) 43 | marcel (~> 1.0.0) 44 | ssrf_filter (~> 1.0) 45 | concurrent-ruby (1.3.5) 46 | connection_pool (2.5.3) 47 | cucumber (10.1.0) 48 | base64 (~> 0.2) 49 | builder (~> 3.2) 50 | cucumber-ci-environment (> 9, < 11) 51 | cucumber-core (> 15, < 17) 52 | cucumber-cucumber-expressions (> 17, < 19) 53 | cucumber-html-formatter (> 20.3, < 22) 54 | diff-lcs (~> 1.5) 55 | logger (~> 1.6) 56 | mini_mime (~> 1.1) 57 | multi_test (~> 1.1) 58 | sys-uname (~> 1.3) 59 | cucumber-ci-environment (10.0.1) 60 | cucumber-core (15.2.1) 61 | cucumber-gherkin (> 27, < 33) 62 | cucumber-messages (> 26, < 30) 63 | cucumber-tag-expressions (> 5, < 7) 64 | cucumber-cucumber-expressions (18.0.1) 65 | bigdecimal 66 | cucumber-gherkin (32.2.0) 67 | cucumber-messages (> 25, < 28) 68 | cucumber-html-formatter (21.14.0) 69 | cucumber-messages (> 19, < 28) 70 | cucumber-messages (27.2.0) 71 | cucumber-tag-expressions (6.1.2) 72 | cucumber_priority (1.1.0) 73 | cucumber 74 | database_cleaner (2.1.0) 75 | database_cleaner-active_record (>= 2, < 3) 76 | database_cleaner-active_record (2.2.2) 77 | activerecord (>= 5.a) 78 | database_cleaner-core (~> 2.0) 79 | database_cleaner-core (2.0.1) 80 | diff-lcs (1.6.2) 81 | drb (2.2.3) 82 | factory_bot (6.5.5) 83 | activesupport (>= 6.1.0) 84 | ffi (1.17.2) 85 | ffi (1.17.2-aarch64-linux-gnu) 86 | ffi (1.17.2-aarch64-linux-musl) 87 | ffi (1.17.2-arm-linux-gnu) 88 | ffi (1.17.2-arm-linux-musl) 89 | ffi (1.17.2-arm64-darwin) 90 | ffi (1.17.2-x86-linux-gnu) 91 | ffi (1.17.2-x86-linux-musl) 92 | ffi (1.17.2-x86_64-darwin) 93 | ffi (1.17.2-x86_64-linux-gnu) 94 | ffi (1.17.2-x86_64-linux-musl) 95 | gemika (1.0.0) 96 | i18n (1.14.7) 97 | concurrent-ruby (~> 1.0) 98 | image_processing (1.14.0) 99 | mini_magick (>= 4.9.5, < 6) 100 | ruby-vips (>= 2.0.17, < 3) 101 | logger (1.7.0) 102 | marcel (1.0.4) 103 | memoist (0.16.2) 104 | mini_magick (5.3.1) 105 | logger 106 | mini_mime (1.1.5) 107 | minitest (5.25.5) 108 | multi_test (1.1.0) 109 | pg (1.6.1) 110 | pg (1.6.1-aarch64-linux) 111 | pg (1.6.1-aarch64-linux-musl) 112 | pg (1.6.1-aarch64-mingw-ucrt) 113 | pg (1.6.1-arm64-darwin) 114 | pg (1.6.1-x86_64-darwin) 115 | pg (1.6.1-x86_64-linux) 116 | pg (1.6.1-x86_64-linux-musl) 117 | public_suffix (6.0.2) 118 | rake (13.3.0) 119 | rspec (3.13.1) 120 | rspec-core (~> 3.13.0) 121 | rspec-expectations (~> 3.13.0) 122 | rspec-mocks (~> 3.13.0) 123 | rspec-core (3.13.5) 124 | rspec-support (~> 3.13.0) 125 | rspec-expectations (3.13.5) 126 | diff-lcs (>= 1.2.0, < 2.0) 127 | rspec-support (~> 3.13.0) 128 | rspec-mocks (3.13.5) 129 | diff-lcs (>= 1.2.0, < 2.0) 130 | rspec-support (~> 3.13.0) 131 | rspec-support (3.13.5) 132 | ruby-vips (2.2.5) 133 | ffi (~> 1.12) 134 | logger 135 | securerandom (0.4.1) 136 | ssrf_filter (1.3.0) 137 | sys-uname (1.4.0) 138 | ffi (~> 1.1) 139 | memoist (~> 0.16.2) 140 | timeout (0.4.3) 141 | tzinfo (2.0.6) 142 | concurrent-ruby (~> 1.0) 143 | uri (1.0.3) 144 | 145 | PLATFORMS 146 | aarch64-linux 147 | aarch64-linux-gnu 148 | aarch64-linux-musl 149 | aarch64-mingw-ucrt 150 | arm-linux-gnu 151 | arm-linux-musl 152 | arm64-darwin 153 | ruby 154 | x86-linux-gnu 155 | x86-linux-musl 156 | x86_64-darwin 157 | x86_64-linux-gnu 158 | x86_64-linux-musl 159 | 160 | DEPENDENCIES 161 | activerecord (~> 8.0.0) 162 | activesupport (~> 8.0.0) 163 | carrierwave 164 | cucumber (~> 10.1.0) 165 | cucumber_factory! 166 | database_cleaner 167 | factory_bot 168 | gemika (>= 0.8.1) 169 | pg (> 1.2.3) 170 | rake (> 10.0) 171 | rspec (~> 3.0) 172 | 173 | BUNDLED WITH 174 | 2.6.3 175 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | ## Unreleased 9 | 10 | ### Breaking changes 11 | 12 | - 13 | 14 | ### Compatible changes 15 | 16 | 17 | ## 2.7.0 - 2025-08-28 18 | 19 | ### Compatible changes 20 | 21 | - Add Support for Cucumber >= 10 22 | 23 | ## 2.6.0 - 2025-01-21 24 | 25 | ### Compatible changes 26 | 27 | - Add Support for Ruby 3.4 28 | - This requires Cucumber 9+ 29 | 30 | ## 2.5.0 - 2022-09-15 31 | 32 | ### Compatible changes 33 | 34 | - Treat transient factory_bot attributes as associations if they are named like a factory. 35 | 36 | ## 2.4.1 - 2022-03-16 37 | 38 | ### Compatible changes 39 | 40 | - Enabled MFA for RubyGems 41 | 42 | ## 2.4.0 - 2021-03-30 43 | 44 | ### Compatible changes 45 | 46 | - Added support for ActiveRecord >= 6. 47 | 48 | ## 2.3.1 - 2020-10-01 49 | 50 | ### Compatible changes 51 | 52 | - Lowered the priority of all steps in this gem to avoid issues with overlapping steps. 53 | 54 | ## 2.3.0 - 2020-09-24 55 | 56 | ### Compatible changes 57 | 58 | - Added a step to add file objects to a model: 59 | ```cucumber 60 | Given there is a user with the avatar file:"path/to/avatar.jpg" 61 | ``` 62 | Both single and double quotes are supported. 63 | 64 | ## 2.2.0 - 2020-09-23 65 | 66 | ### Compatible changes 67 | 68 | - A step was added that allows modifying existing records with a similar syntax to creating new records: 69 | ```cucumber 70 | (Given "Bob" is a user) 71 | And "Bob" has the email "foo@bar.com" and is subscribed 72 | ``` 73 | - This step will also work with doc strings or tables: 74 | ```cucumber 75 | (Given "Bob" is a user) 76 | And the user above has these attributes: 77 | | name | Bob | 78 | | email | foo@bar.com | 79 | ``` 80 | 81 | ## 2.1.1 - 2020-05-20 82 | 83 | ### Compatible changes 84 | 85 | - Cucumber 2.1.0 introduced some regressions which are being addressed with this patch: 86 | - Fix the assignment of polymorphic associations. 87 | - Restore the support for inherited traits within nested factories. 88 | 89 | ## 2.1.0 - 2020-03-09 90 | 91 | ### Compatible changes 92 | 93 | - Allow associations to be set for [transient attributes](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#transient-attributes) if they are named after the model. For example, when there is a `Role` model and the`user` factory has a transient attribute `role`, the following steps are now valid: 94 | ``` 95 | Given there is a role 96 | And there is a user with the role above 97 | ``` 98 | 99 | ## 2.0.2 - 2020-03-26 100 | 101 | ### Compatible changes 102 | 103 | - Removed development and test support for Ruby 1.8. Closes #32. 104 | 105 | ## 2.0.1 - 2020-02-27 106 | 107 | ### Compatible changes 108 | 109 | - Fix a bug that prevented created records to be named when using multiline attribute assignments 110 | ``` 111 | Given "Bob" is a user with these attributes: 112 | | email | foo@bar.com | 113 | ``` 114 | 115 | ## 2.0.0 - 2020-02-10 116 | 117 | ### Breaking changes 118 | 119 | - CucumberFactory now raises an `ArgumentError` if some parts of a matched step were not used. For example, while this step was accepted in recent versions, it will now complain with the message `Unable to parse attributes " and the ".`: 120 | ``` 121 | Given there is a user with the attribute 'foo' and the 122 | ``` 123 | 124 | 125 | ### Compatible changes 126 | 127 | - Single quoted attribute values and model names are now supported. Example: 128 | 129 | ``` 130 | Given 'jack' is a user with the first name 'Jack' 131 | ``` 132 | 133 | ## 1.15.1 - 2019-05-30 134 | 135 | ### Compatible changes 136 | 137 | - Fix: Allow to use array assignments within a doc string or table assignment 138 | 139 | Example: 140 | 141 | ``` 142 | Given there is a post with these attributes: 143 | |tags| ["urgent", "vip"] | 144 | ``` 145 | 146 | ## 1.15.0 - 2019-02-08 147 | 148 | ### Breaking changes 149 | 150 | - Moved gem's code from `Cucumber` module into `CucumberFactory` module. 151 | - Instead of calling `Cucumber::Factory.add_steps(self)`, `require 'cucumber_factory/add_steps'` instead. 152 | 153 | 154 | ## 1.14.2 - 2018-10-31 155 | 156 | ### Compatible changes 157 | 158 | - Replace deprecated `Fixnum` with `Integer` 159 | 160 | 161 | ## 1.14.1 - 2018-10-26 162 | 163 | ### Compatible changes 164 | 165 | - Allow to refer to previously set foreign keys 166 | 167 | 168 | ## 1.14.0 - 2018-10-26 169 | 170 | ### Compatible changes 171 | 172 | - Allow to set numbers without quotes 173 | - Support array fields out of the box 174 | - Allow to set has_many associations with square bracket notation 175 | 176 | 177 | ## 1.13.0 - 2018-04-26 178 | 179 | ### Compatible changes 180 | 181 | - Support multi line attribute assignment 182 | 183 | 184 | ## 1.12.0 - 2018-04-26 185 | 186 | ### Compatible changes 187 | 188 | - Support for Cucumber 3.0 and 3.1 189 | 190 | 191 | ## Previous releases 192 | 193 | - See [GitHub](https://github.com/makandra/cucumber_factory/commits/master) 194 | -------------------------------------------------------------------------------- /lib/cucumber_factory/build_strategy.rb: -------------------------------------------------------------------------------- 1 | module CucumberFactory 2 | 3 | # wraps machinist / factory_bot / ruby object logic 4 | 5 | class BuildStrategy 6 | 7 | class << self 8 | 9 | def from_prose(model_prose, variant_prose) 10 | variants = variants_from_prose(variant_prose) 11 | factory = factory_bot_factory(model_prose, variants) 12 | 13 | if factory 14 | factory.compile # Required to load inherited traits! 15 | strategy = factory_bot_strategy(factory, model_prose, variants) 16 | transient_attributes = factory_bot_transient_attributes(factory, variants) 17 | else 18 | strategy = alternative_strategy(model_prose, variants) 19 | transient_attributes = [] 20 | end 21 | 22 | [strategy, transient_attributes] 23 | end 24 | 25 | def class_from_factory(model_prose) 26 | if (factory = factory_bot_factory(model_prose, [])) 27 | factory.build_class 28 | end 29 | end 30 | 31 | def parse_model_class(model_prose) 32 | underscored_model_name(model_prose).camelize.constantize 33 | end 34 | 35 | private 36 | 37 | def variants_from_prose(variant_prose) 38 | if variant_prose.present? 39 | variants = /\((.*?)\)/.match(variant_prose)[1].split(/\s*,\s*/) 40 | variants.collect { |variant| variant.downcase.gsub(" ", "_").to_sym } 41 | else 42 | [] 43 | end 44 | end 45 | 46 | def factory_bot_factory(model_prose, variants) 47 | return unless factory_bot_class 48 | 49 | factories = factory_bot_class.factories 50 | 51 | factory_name = factory_name_from_prose(model_prose) 52 | factory = factories.registered?(factory_name) && factories[factory_name] 53 | 54 | if factory.nil? && variants.present? 55 | factory = factories.registered?(variants[0]) && factories[variants[0]] 56 | end 57 | 58 | factory 59 | end 60 | 61 | def factory_bot_strategy(factory, model_prose, variants) 62 | return unless factory 63 | 64 | factory_name = factory_name_from_prose(model_prose) 65 | if factory_bot_class.factories[factory_name].nil? && variants.present? 66 | factory_name, *variants = variants 67 | end 68 | 69 | new(factory.build_class) do |attributes| 70 | # Cannot have additional scalar args after a varargs 71 | # argument in Ruby 1.8 and 1.9 72 | args = [] 73 | args += variants 74 | args << attributes 75 | factory_bot_class.create(factory_name, *args) 76 | end 77 | end 78 | 79 | def factory_bot_transient_attributes(factory, variants) 80 | return [] unless factory 81 | 82 | factory_attributes = factory_bot_attributes(factory, variants) 83 | class_attributes = factory.build_class.attribute_names.map(&:to_sym) 84 | 85 | factory_attributes - class_attributes 86 | end 87 | 88 | def factory_bot_attributes(factory, variants) 89 | traits = factory_bot_traits(factory, variants) 90 | factory.with_traits(traits.map(&:name)).definition.attributes.names 91 | end 92 | 93 | def factory_bot_traits(factory, variants) 94 | factory.definition.defined_traits.select do |trait| 95 | variants.include?(trait.name.to_sym) 96 | end 97 | end 98 | 99 | def alternative_strategy(model_prose, variants) 100 | model_class = parse_model_class(model_prose) 101 | machinist_strategy(model_class, variants) || 102 | active_record_strategy(model_class) || 103 | ruby_object_strategy(model_class) 104 | end 105 | 106 | def machinist_strategy(model_class, variants) 107 | return unless model_class.respond_to?(:make) 108 | 109 | new(model_class) do |attributes| 110 | if variants.present? 111 | variants.size == 1 or raise 'Machinist only supports a single variant per blueprint' 112 | model_class.make(variants.first, attributes) 113 | else 114 | model_class.make(attributes) 115 | end 116 | end 117 | end 118 | 119 | def active_record_strategy(model_class) 120 | return unless model_class.respond_to?(:create!) 121 | 122 | new(model_class) do |attributes| 123 | model = model_class.new 124 | CucumberFactory::Switcher.assign_attributes(model, attributes) 125 | model.save! 126 | model 127 | end 128 | end 129 | 130 | def ruby_object_strategy(model_class) 131 | new(model_class) do |attributes| 132 | model_class.new(attributes) 133 | end 134 | end 135 | 136 | def factory_bot_class 137 | factory_class = ::FactoryBot if defined?(FactoryBot) 138 | factory_class ||= ::FactoryGirl if defined?(FactoryGirl) 139 | factory_class 140 | end 141 | 142 | def factory_name_from_prose(model_prose) 143 | underscored_model_name(model_prose).to_s.underscore.gsub('/', '_').to_sym 144 | end 145 | 146 | def underscored_model_name(model_prose) 147 | # don't use \w which depends on the system locale 148 | model_prose.gsub(/[^A-Za-z0-9_\/]+/, "_") 149 | end 150 | 151 | end 152 | 153 | 154 | attr_reader :model_class 155 | 156 | def initialize(model_class, &block) 157 | @model_class = model_class 158 | @create_proc = block 159 | end 160 | 161 | def create_record(attributes) 162 | @create_proc.call(attributes) 163 | end 164 | 165 | end 166 | 167 | end 168 | -------------------------------------------------------------------------------- /media/makandra-with-bottom-margin.dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 50 | 56 | 59 | 64 | 70 | 76 | 82 | 88 | 89 | 98 | 99 | 108 | 109 | 118 | 119 | 128 | 129 | 138 | 139 | 148 | 150 | 155 | 160 | 161 | 167 | 173 | 179 | 180 | -------------------------------------------------------------------------------- /media/makandra-with-bottom-margin.light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 50 | 56 | 59 | 64 | 70 | 76 | 82 | 88 | 89 | 98 | 99 | 108 | 109 | 118 | 119 | 128 | 129 | 138 | 139 | 148 | 150 | 155 | 160 | 161 | 167 | 173 | 179 | 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | makandra 7 | 8 | 9 | 10 | 11 | 12 | 13 | cucumber_factory 14 | 15 |

16 | 17 | [![Tests](https://github.com/makandra/cucumber_factory/workflows/Tests/badge.svg)](https://github.com/makandra/cucumber_factory/actions?query=branch:master) 18 | 19 | 20 | Create ActiveRecord objects without step definitions 21 | ---------------------------------------------------- 22 | 23 | cucumber_factory allows you to create ActiveRecord objects directly from your [Cucumber](http://cukes.info/) features. No step definitions required. 24 | 25 | 26 | Basic usage 27 | ----------- 28 | 29 | To create a new record with default attributes, begin any step with `Given there is`: 30 | 31 | ```cucumber 32 | Given there is a movie 33 | ``` 34 | 35 | To create the record, cucumber_factory will call [`FactoryBot.create(:movie)`](http://github.com/thoughtbot/factory_bot), `FactoryGirl.create(:movie)`, [`Movie.make`](http://github.com/notahat/machinist), [`Movie.create!`](http://apidock.com/rails/ActiveRecord/Persistence/ClassMethods/create%21) or `Movie.new`, depending on what's available. 36 | 37 | Quoted strings and numbers denote attribute values: 38 | 39 | ```cucumber 40 | Given there is a movie with the title "Sunshine" and the year 2007 41 | ``` 42 | 43 | To update an existing record, specify the record and the changes: 44 | ``` 45 | Given the movie above has the title "Sunset" and the year 2008 46 | Given the movie "Sunrise" has the year 2009 47 | ``` 48 | A record can be specified by the `above` keyword, which uses the last created record of this class, or by any string that was used during its creation. 49 | 50 | Setting boolean attributes 51 | -------------------------- 52 | 53 | Boolean attributes can be set by appending `which`, `that` or `who` at the end: 54 | 55 | ```cucumber 56 | Given there is a movie which is awesome 57 | And there is a movie with the name "Sunshine" that is not a comedy 58 | And there is a director who is popular 59 | ``` 60 | 61 | Instead of `and` you can also use `but` and commas to join sentences: 62 | 63 | ```cucumber 64 | Given there is a movie which is awesome, popular and successful but not science fiction 65 | And there is a director with the income "500000" but with the account balance "-30000" 66 | ``` 67 | 68 | To update boolean attributes use the keyword `is`: 69 | 70 | ```cucumber 71 | Given the movie above is awesome but not popular 72 | Given the movie above has the year 1979 but is not science fiction 73 | ``` 74 | 75 | 76 | Setting many attributes with a table 77 | ------------------------------------ 78 | 79 | If you have many attribute assignments you can use doc string or data table: 80 | 81 | ```cucumber 82 | Given there is a movie with these attributes: 83 | """ 84 | name: Sunshine 85 | comedy: false 86 | """ 87 | ``` 88 | 89 | ```cucumber 90 | Given there is a movie with these attributes: 91 | | name | Sunshine | 92 | | comedy | false | 93 | ``` 94 | 95 | ```cucumber 96 | Given the movie above has these attributes: 97 | """ 98 | name: Sunshine 99 | comedy: false 100 | """ 101 | ``` 102 | 103 | Setting associations 104 | -------------------- 105 | 106 | You can set `belongs_to` and `transient` associations by referring to the last created record of as `above`: 107 | 108 | ```cucumber 109 | Given there is a movie with the title "Before Sunrise" 110 | And there is a movie with the prequel above 111 | ``` 112 | 113 | The example above also shows how to set `has_many` associations - you simply set the `belongs_to` association on the other side. 114 | 115 | You can also refer to a previously created record using any string attribute used in its creation: 116 | 117 | ```cucumber 118 | Given there is a movie with the title "Before Sunrise" 119 | And there is a movie with the title "Limitless" 120 | And there is a movie with the prequel "Before Sunrise" 121 | ``` 122 | 123 | You can also explicitly give a record a name and use it to set a `belongs_to` association below: 124 | 125 | ```cucumber 126 | Given "Before Sunrise" is a movie 127 | And there is a movie with the title "Limitless" 128 | And there is a movie with the prequel "Before Sunrise" 129 | ``` 130 | 131 | Note that in the example above, "Before Sunrise" is only a name you can use to refer to the record. The name is not actually used for the movie title, or any other attribute value. 132 | 133 | It is not possible to define associations in doc string or data table, but you can combine them in one 134 | step: 135 | 136 | ```cucumber 137 | Given there is a movie with the prequel above and these attributes: 138 | """ 139 | name: Sunshine 140 | comedy: false 141 | """ 142 | ``` 143 | 144 | ```cucumber 145 | Given there is a movie with the prequel above and these attributes: 146 | | name | Sunshine | 147 | | comedy | false | 148 | ``` 149 | 150 | 151 | Setting array attributes or has_many associations 152 | ------------------------------------------------- 153 | 154 | You can set `has_many` associations by referring to multiple named records in square brackets: 155 | 156 | ```cucumber 157 | Given there is a movie with the title "Sunshine" 158 | And there is a movie with the title "Limitless" 159 | And there is a movie with the title "Salt" 160 | And there is a user with the favorite movies ["Sunshine", "Limitless" and "Salt"] 161 | ``` 162 | 163 | When using [PostgreSQL array columns](https://www.postgresql.org/docs/9.1/static/arrays.html), you can set an array attribute to a value with square brackets: 164 | 165 | ```cucumber 166 | Given there is a movie with the tags ["comedy", "drama" and "action"] 167 | ``` 168 | 169 | 170 | Setting file attributes 171 | ----------------------- 172 | 173 | You can set an attribute to a file object with the following syntax: 174 | 175 | ```cucumber 176 | Given there is a movie with the image file:'path/to/image.jpg' 177 | ``` 178 | 179 | All paths are relative to the project root, absolute paths are not supported. Please note that file attributes must follow the syntax `file:"PATH"`, both single and double quotes are allowed. 180 | 181 | Using named factories and traits 182 | -------------------------------- 183 | 184 | You can use a [FactoryBot child factory](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#inheritance) or [Machinist named blueprint](https://github.com/notahat/machinist/tree/1.0-maintenance#named-blueprints) by putting the variant name in parentheses: 185 | 186 | ```cucumber 187 | Given there is a movie (comedy) with the title "Groundhog Day" 188 | ``` 189 | 190 | You can use [FactoryBot traits](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#traits) by putting the traits in parentheses, as a comma-separated list: 191 | 192 | ```cucumber 193 | Given there is a movie (moody, dark) with the title "Interstellar" 194 | ``` 195 | 196 | 197 | 198 | 199 | Overriding factory steps 200 | ------------------------ 201 | 202 | If you want to override a factory step with your own version, just do so: 203 | 204 | ```ruby 205 | Given /^there is a movie with good actors$/ do 206 | movie = Movie.make 207 | movie.actors << Actor.make(:name => 'Clive Owen') 208 | movie.actors << Actor.make(:name => 'Denzel Washington') 209 | end 210 | ``` 211 | 212 | Custom steps will always be preferred over factory steps. Also Cucumber will not raise a warning about ambiguous steps if the only other matching step is a factory step. Thanks, [cucumber_priority](https://github.com/makandra/cucumber_priority)! 213 | 214 | 215 | Supported Cucumber versions 216 | ---------------------------- 217 | 218 | cucumber_factory is tested against Cucumber 1.3, 2.4, 3.0, 3.1, 4.1, 5.3, 9.2 and 10.1. 219 | 220 | 221 | Installation 222 | ------------ 223 | 224 | In your `Gemfile` say: 225 | 226 | gem 'cucumber_factory' 227 | 228 | Now create a file `features/step_definitions/factory_steps.rb`, which just says 229 | 230 | require 'cucumber_factory/add_steps' 231 | 232 | Now run `bundle install` and restart your server. 233 | 234 | 235 | Development 236 | ----------- 237 | 238 | There are tests in `spec`. We only accept PRs with tests. To run tests: 239 | 240 | - Install the Ruby version stated in `.ruby-version` 241 | - Create a local PostgreSQL database: 242 | ``` 243 | $ sudo -u postgres psql -c 'create database cucumber_factory_test;' 244 | ``` 245 | 246 | - Copy `spec/support/database.sample.yml` to `spec/support/database.yml` and enter your local credentials for the test databases 247 | - Install development dependencies using `bundle install` 248 | - Run tests with the default symlinked Gemfile using `bundle exec rspec` or explicit with `BUNDLE_GEMFILE=Gemfile.cucumber-x.x bundle exec rspec spec` 249 | 250 | We recommend to test large changes against multiple versions of Ruby and multiple dependency sets. Supported combinations are configured in .github/workflows/test.yml. We provide some rake tasks to help with this: 251 | 252 | For each ruby version do (you need to change it manually): 253 | - Install development dependencies using `rake matrix:install` 254 | - Run tests using `rake matrix:spec` 255 | - Shorthand: 256 | ```sh 257 | for version in "2.5.3" "2.6.6" "2.7.2" "3.2.0" "3.4.1"; do rbenv shell $version && bundle install && bundle exec rake matrix:install && bundle exec rake matrix:spec; done 258 | ``` 259 | 260 | Note that we have configured GitHub Actions to automatically run tests in all supported Ruby versions and dependency sets after each push. We will only merge pull requests after a green workflow build. 261 | 262 | If you would like to contribute: 263 | 264 | - Fork the repository. 265 | - Push your changes **with passing specs**. 266 | - Send us a pull request. 267 | 268 | I'm very eager to keep this gem leightweight and on topic. If you're unsure whether a change would make it into the gem [talk to me beforehand](mailto:henning.koch@makandra.de). 269 | 270 | 271 | Credits 272 | ------- 273 | 274 | Henning Koch from [makandra](https://makandra.com/) 275 | -------------------------------------------------------------------------------- /lib/cucumber_factory/factory.rb: -------------------------------------------------------------------------------- 1 | module CucumberFactory 2 | module Factory 3 | class Error < StandardError; end 4 | 5 | ATTRIBUTES_PATTERN = '( with the .+?)?( (?:which|who|that) is .+?)?' # ... with the year 1979 which is science fiction 6 | TEXT_ATTRIBUTES_PATTERN = ' (?:with|and) these attributes:' 7 | UPDATE_ATTR_PATTERN = '(?: (?:has|belongs to)( the .+?)|(?: and| but|,)*( is .+?))' # ... belongs to the collection "Fantasy" and is trending 8 | TEXT_UPDATE_ATTR_PATTERN = '(?: and|,)* has these attributes:' 9 | 10 | RECORD_PATTERN = 'there is an? (.+?)( \(.+?\))?' # Given there is a movie (comedy) 11 | NAMED_RECORD_PATTERN = '(?:"([^\"]*)"|\'([^\']*)\') is an? (.+?)( \(.+?\))?' # Given "LotR" is a movie 12 | RECORD_UPDATE_PATTERN = 'the ([^"\',]+?) (above|".+?"|\'.+?\')' # Given the movie "LotR" ... 13 | 14 | NAMED_RECORDS_VARIABLE = :'@named_cucumber_factory_records' 15 | 16 | VALUE_INTEGER = /\d+/ 17 | VALUE_DECIMAL = /[\d\.]+/ 18 | VALUE_STRING = /"[^"]*"|'[^']*'/ 19 | VALUE_FILE = /file:#{VALUE_STRING}/ 20 | VALUE_ARRAY = /\[[^\]]*\]/ 21 | VALUE_LAST_RECORD = /\babove\b/ 22 | 23 | VALUE_SCALAR = /#{VALUE_STRING}|#{VALUE_DECIMAL}|#{VALUE_INTEGER}|#{VALUE_FILE}/ 24 | 25 | CLEAR_NAMED_RECORDS_STEP_DESCRIPTOR = { 26 | :kind => :Before, 27 | :block => proc { CucumberFactory::Factory.send(:reset_named_records, self) } 28 | } 29 | 30 | # We cannot use vararg blocks in the descriptors in Ruby 1.8, as explained by 31 | # Aslak: http://www.ruby-forum.com/topic/182927. We use different descriptors and cucumber priority to work around 32 | # it. 33 | 34 | NAMED_CREATION_STEP_DESCRIPTOR = { 35 | :kind => :Given, 36 | :pattern => /^#{NAMED_RECORD_PATTERN}#{ATTRIBUTES_PATTERN}?$/, 37 | :block => lambda { |a1, a2, a3, a4, a5, a6| CucumberFactory::Factory.send(:parse_named_creation, self, a1, a2, a3, a4, a5, a6) } 38 | } 39 | 40 | CREATION_STEP_DESCRIPTOR = { 41 | :kind => :Given, 42 | :pattern => /^#{RECORD_PATTERN}#{ATTRIBUTES_PATTERN}$/, 43 | :block => lambda { |a1, a2, a3, a4| CucumberFactory::Factory.send(:parse_creation, self, a1, a2, a3, a4) } 44 | } 45 | 46 | UPDATE_STEP_DESCRIPTOR = { 47 | :kind => :And, 48 | :pattern => /^#{RECORD_UPDATE_PATTERN}#{UPDATE_ATTR_PATTERN}+$/, 49 | :block => lambda { |a1, a2, a3, a4| CucumberFactory::Factory.send(:parse_update, self, a1, a2, a3, a4) } 50 | } 51 | 52 | NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = { 53 | :kind => :Given, 54 | :pattern => /^#{NAMED_RECORD_PATTERN}#{ATTRIBUTES_PATTERN}#{TEXT_ATTRIBUTES_PATTERN}?$/, 55 | :block => lambda { |a1, a2, a3, a4, a5, a6, a7| CucumberFactory::Factory.send(:parse_named_creation, self, a1, a2, a3, a4, a5, a6, a7) }, 56 | :priority => true 57 | } 58 | 59 | CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = { 60 | :kind => :Given, 61 | :pattern => /^#{RECORD_PATTERN}#{ATTRIBUTES_PATTERN}#{TEXT_ATTRIBUTES_PATTERN}$/, 62 | :block => lambda { |a1, a2, a3, a4, a5| CucumberFactory::Factory.send(:parse_creation, self, a1, a2, a3, a4, a5) }, 63 | :priority => true 64 | } 65 | 66 | UPDATE_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = { 67 | :kind => :And, 68 | :pattern => /^#{RECORD_UPDATE_PATTERN}#{UPDATE_ATTR_PATTERN}*#{TEXT_UPDATE_ATTR_PATTERN}$/, 69 | :block => lambda { |a1, a2, a3, a4, a5| CucumberFactory::Factory.send(:parse_update, self, a1, a2, a3, a4, a5) }, 70 | :priority => true 71 | } 72 | 73 | class << self 74 | 75 | def add_steps(main) 76 | add_step(main, CREATION_STEP_DESCRIPTOR) 77 | add_step(main, NAMED_CREATION_STEP_DESCRIPTOR) 78 | add_step(main, CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES) 79 | add_step(main, NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES) 80 | add_step(main, CLEAR_NAMED_RECORDS_STEP_DESCRIPTOR) 81 | add_step(main, UPDATE_STEP_DESCRIPTOR) 82 | add_step(main, UPDATE_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES) 83 | end 84 | 85 | private 86 | 87 | def add_step(main, descriptor) 88 | main.instance_eval { 89 | kind = descriptor[:kind] 90 | object = send(kind, *[descriptor[:pattern]].compact, &descriptor[:block]) 91 | # cucumber_factory steps get a low priority due to their generic syntax 92 | object.overridable(:priority => descriptor[:priority] ? -1 : -2) if kind != :Before 93 | object 94 | } 95 | end 96 | 97 | def reset_named_records(world) 98 | world.instance_variable_set(NAMED_RECORDS_VARIABLE, {}) 99 | end 100 | 101 | def named_records(world) 102 | hash = world.instance_variable_get(NAMED_RECORDS_VARIABLE) 103 | hash || reset_named_records(world) 104 | end 105 | 106 | def get_named_record(world, name) 107 | named_records(world)[name].tap do |record| 108 | record.reload if record.respond_to?(:reload) and record.respond_to?(:new_record?) and not record.new_record? 109 | end 110 | end 111 | 112 | def set_named_record(world, name, record) 113 | named_records(world)[name] = record 114 | end 115 | 116 | def parse_named_creation(world, double_quote_name, single_quote_name, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil) 117 | record = parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes) 118 | name = [double_quote_name, single_quote_name].compact.first 119 | set_named_record(world, name, record) 120 | end 121 | 122 | def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil) 123 | build_strategy, transient_attributes = CucumberFactory::BuildStrategy.from_prose(raw_model, raw_variant) 124 | model_class = build_strategy.model_class 125 | attributes = parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes, transient_attributes) 126 | record = build_strategy.create_record(attributes) 127 | remember_record_names(world, record, attributes) 128 | record 129 | end 130 | 131 | def parse_update(world, raw_model, raw_name, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil) 132 | model_class = CucumberFactory::BuildStrategy.parse_model_class(raw_model) 133 | attributes = parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes) 134 | record = resolve_associated_value(world, model_class, model_class, model_class, raw_name) 135 | CucumberFactory::UpdateStrategy.new(record).assign_attributes(attributes) 136 | remember_record_names(world, record, attributes) 137 | record 138 | end 139 | 140 | def parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil, transient_attributes = []) 141 | attributes = {} 142 | if raw_attributes.try(:strip).present? 143 | raw_attribute_fragment_regex = /(?:the |and |with |but |,| )+(.*?) (#{VALUE_SCALAR}|#{VALUE_ARRAY}|#{VALUE_LAST_RECORD})/ 144 | raw_attributes.scan(raw_attribute_fragment_regex).each do |fragment| 145 | attribute = attribute_name_from_prose(fragment[0]) 146 | value = fragment[1] 147 | attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value) 148 | end 149 | unused_raw_attributes = raw_attributes.gsub(raw_attribute_fragment_regex, '') 150 | if unused_raw_attributes.present? 151 | raise ArgumentError, "Unable to parse attributes #{unused_raw_attributes.inspect}." 152 | end 153 | end 154 | if raw_boolean_attributes.try(:strip).present? 155 | raw_boolean_attributes.scan(/(?:which|who|that|is| )*(not )?(.+?)(?: and | but |,|$)+/).each do |fragment| 156 | flag = !fragment[0] # if the word 'not' didn't match above, this expression is true 157 | attribute = attribute_name_from_prose(fragment[1]) 158 | attributes[attribute] = flag 159 | end 160 | end 161 | if raw_multiline_attributes.present? 162 | # DocString e.g. "first name: Jane\nlast name: Jenny\n" 163 | if raw_multiline_attributes.is_a?(String) 164 | raw_multiline_attributes.split("\n").each do |fragment| 165 | raw_attribute, value = fragment.split(': ', 2) 166 | attribute = attribute_name_from_prose(raw_attribute) 167 | value = "\"#{value}\"" unless matches_fully?(value, /#{VALUE_ARRAY}|#{VALUE_FILE}/) 168 | attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value) 169 | end 170 | # DataTable e.g. in raw [["first name", "Jane"], ["last name", "Jenny"]] 171 | else 172 | raw_multiline_attributes.raw.each do |raw_attribute, value| 173 | attribute = attribute_name_from_prose(raw_attribute) 174 | value = "\"#{value}\"" unless matches_fully?(value, /#{VALUE_ARRAY}|#{VALUE_FILE}/) 175 | attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value) 176 | end 177 | end 178 | end 179 | attributes 180 | end 181 | 182 | def attribute_value(world, model_class, transient_attributes, attribute, value) 183 | associated, association_class = resolve_association(attribute, model_class, transient_attributes) 184 | 185 | value = if matches_fully?(value, VALUE_ARRAY) 186 | array_values = unquote(value).scan(VALUE_SCALAR) 187 | array_values.map { |v| attribute_value(world, model_class, transient_attributes, attribute, v) } 188 | elsif associated 189 | resolve_associated_value(world, model_class, association_class, attribute, value) 190 | else 191 | resolve_scalar_value(world, model_class, attribute, value) 192 | end 193 | value 194 | end 195 | 196 | def resolve_association(attribute, model_class, transient_attributes) 197 | return unless model_class.respond_to?(:reflect_on_association) 198 | 199 | association = model_class.reflect_on_association(attribute) 200 | association_class = nil 201 | 202 | if association 203 | association_class = association.klass unless association.polymorphic? 204 | associated = true 205 | elsif transient_attributes.include?(attribute.to_sym) 206 | klass_name = attribute.to_s.camelize 207 | if Object.const_defined?(klass_name) 208 | association_class = klass_name.constantize 209 | associated = true 210 | elsif (factory_class = CucumberFactory::BuildStrategy.class_from_factory(attribute.to_s)) 211 | association_class = factory_class 212 | associated = true 213 | end 214 | else 215 | associated = false 216 | end 217 | [associated, association_class] 218 | end 219 | 220 | def resolve_associated_value(world, model_class, association_class, attribute, value) 221 | if matches_fully?(value, VALUE_LAST_RECORD) 222 | raise(Error, "Cannot set last #{model_class}##{attribute} for polymorphic associations") unless association_class.present? 223 | 224 | CucumberFactory::Switcher.find_last(association_class) || raise(Error, "There is no last #{attribute}") 225 | elsif matches_fully?(value, VALUE_STRING) 226 | value = unquote(value) 227 | get_named_record(world, value) || transform_value(world, value) 228 | elsif matches_fully?(value, VALUE_INTEGER) 229 | value = value.to_s 230 | get_named_record(world, value) || transform_value(world, value) 231 | else 232 | raise Error, "Cannot set association #{model_class}##{attribute} to #{value}." 233 | end 234 | end 235 | 236 | def resolve_scalar_value(world, model_class, attribute, value) 237 | if matches_fully?(value, VALUE_STRING) 238 | value = unquote(value) 239 | value = transform_value(world, value) 240 | elsif matches_fully?(value, VALUE_INTEGER) 241 | value = value.to_i 242 | elsif matches_fully?(value, VALUE_DECIMAL) 243 | value = BigDecimal(value) 244 | elsif matches_fully?(value, VALUE_FILE) 245 | path = File.path("./#{file_value_to_path(value)}") 246 | value = File.new(path) 247 | else 248 | raise Error, "Cannot set attribute #{model_class}##{attribute} to #{value}." 249 | end 250 | value 251 | end 252 | 253 | def unquote(string) 254 | # This method removes quotes or brackets from the start and end from a string 255 | # Examples: 'single' => single, "double" => double, [1, 2, 3] => 1, 2, 3 256 | string[1, string.length - 2] 257 | end 258 | 259 | def file_value_to_path(string) 260 | # file paths are marked with a special keyword and enclosed with quotes. 261 | # Example: file:"/path/image.png" 262 | # This will extract the path (/path/image.png) from the text fragment above 263 | unquote string.sub(/\Afile:/, '') 264 | end 265 | 266 | def full_regexp(partial_regexp) 267 | Regexp.new('\\A(?:' + partial_regexp.source + ')\\z', partial_regexp.options) 268 | end 269 | 270 | def matches_fully?(string, partial_regexp) 271 | string =~ full_regexp(partial_regexp) 272 | end 273 | 274 | def transform_value(world, value) 275 | # Transforms were a feature available in Cucumber 1 and 2. 276 | # They have been kind-of replaced by ParameterTypes, which don't work with generic steps 277 | # like CucumberFactory's. 278 | # https://app.cucumber.pro/projects/cucumber-ruby/documents/branch/master/features/docs/writing_support_code/parameter_types.feature 279 | if world.respond_to?(:Transform) 280 | world.Transform(value) 281 | else 282 | value 283 | end 284 | end 285 | 286 | def attribute_name_from_prose(prose) 287 | prose.downcase.gsub(' ', '_').to_sym 288 | end 289 | 290 | def remember_record_names(world, record, attributes) 291 | rememberable_values = attributes.values.select { |v| v.is_a?(String) || v.is_a?(Integer) } 292 | for value in rememberable_values 293 | set_named_record(world, value.to_s, record) 294 | end 295 | end 296 | 297 | end 298 | end 299 | end 300 | -------------------------------------------------------------------------------- /media/logo.light.shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 28 | 32 | 36 | 37 | 47 | 48 | 71 | 73 | 74 | 76 | image/svg+xml 77 | 79 | 80 | 81 | 82 | 83 | 88 | 92 | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | 157 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /media/logo.dark.shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 28 | 32 | 36 | 37 | 47 | 48 | 74 | 76 | 77 | 79 | image/svg+xml 80 | 82 | 83 | 84 | 85 | 86 | 91 | 95 | 99 | 103 | 107 | 111 | 115 | 119 | 123 | 127 | 131 | 135 | 139 | 143 | 147 | 151 | 155 | 159 | 160 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /spec/cucumber_factory/steps_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | TRANSFORMS_SUPPORTED = Gem::Version.new(Cucumber::VERSION) < Gem::Version.new('3.0') 4 | 5 | describe 'steps provided by cucumber_factory' do 6 | before(:each) do 7 | prepare_cucumber_example 8 | end 9 | 10 | context 'FactoryBot' do 11 | it "should create ActiveRecord models by calling #new and #save!" do 12 | movie = Movie.new 13 | Movie.should_receive(:new).with(no_args).and_return(movie) 14 | movie.should_receive(:save!) 15 | invoke_cucumber_step("there is a movie") 16 | end 17 | 18 | it "should create models that have a factory_bot factory by calling #FactoryBot.create(:model_name)" do 19 | FactoryBot.should_receive(:create).with(:job_offer, { :title => "Awesome job" }) 20 | invoke_cucumber_step('there is a job offer with the title "Awesome job"') 21 | end 22 | 23 | it "should create model variants that have a factory_bot factory by calling #FactoryBot.create(:variant)" do 24 | FactoryBot.should_receive(:create).with(:job_offer, :tempting_job_offer, { :title => "Awesomafiablyfantasmic job" }) 25 | invoke_cucumber_step('there is a job offer (tempting job offer) with the title "Awesomafiablyfantasmic job"') 26 | end 27 | 28 | it "should create model variants that have a factory_bot trait by calling #FactoryBot.create(:factory, :trait1, :trait2)" do 29 | FactoryBot.should_receive(:create).with(:job_offer, :risky, :lucrative, { :title => "Awesomafiablyfantasmic job" }) 30 | invoke_cucumber_step('there is a job offer (risky, lucrative) with the title "Awesomafiablyfantasmic job"') 31 | end 32 | 33 | it "should create model variants that have a factory_bot factory by using the model name as a factory name" do 34 | FactoryBot.should_receive(:create).with(:job_offer, { :title => "Awesomafiablyfantasmic job" }) 35 | invoke_cucumber_step('there is a job offer with the title "Awesomafiablyfantasmic job"') 36 | end 37 | 38 | it "should instantiate classes with multiple words in their name" do 39 | job_offer = JobOffer.new 40 | JobOffer.should_receive(:new).with(no_args).and_return(job_offer) 41 | invoke_cucumber_step("there is a job offer") 42 | end 43 | 44 | it "should instantiate classes with uppercase characters in their name" do 45 | user = User.new 46 | User.should_receive(:new).and_return(user) 47 | invoke_cucumber_step("there is a User") 48 | end 49 | 50 | it "should allow either 'a' or 'an' for the article" do 51 | opera = Opera.new 52 | Opera.should_receive(:new).with(no_args).and_return(opera) 53 | invoke_cucumber_step("there is an opera") 54 | end 55 | 56 | it "should create records with attributes" do 57 | movie = Movie.new 58 | Movie.stub(:new => movie) 59 | invoke_cucumber_step('there is a movie with the title "Sunshine" and the year "2007"') 60 | movie.title.should == "Sunshine" 61 | movie.year.should == 2007 62 | end 63 | 64 | it "should allow to join attribute lists with 'and's, commas and 'but's" do 65 | movie = Movie.new 66 | Movie.stub(:new => movie) 67 | invoke_cucumber_step('there is a movie with the title "Sunshine", the year "2007" but with the box office result "32000000"') 68 | movie.title.should == "Sunshine" 69 | movie.year.should == 2007 70 | movie.box_office_result.should == 32000000 71 | end 72 | 73 | if TRANSFORMS_SUPPORTED 74 | it "should apply Cucumber transforms to attribute values" do 75 | movie = Movie.new 76 | Movie.stub(:new => movie) 77 | @main.instance_eval do 78 | Transform /^(value)$/ do |value| 79 | 'transformed value' 80 | end 81 | end 82 | invoke_cucumber_step('there is a movie with the title "value"') 83 | movie.title.should == "transformed value" 84 | end 85 | end 86 | 87 | it "should create records with attributes containing spaces" do 88 | movie = Movie.new 89 | Movie.stub(:new => movie) 90 | invoke_cucumber_step('there is a movie with the box office result "99999999"') 91 | movie.box_office_result.should == 99999999 92 | end 93 | 94 | it "should create records with attributes containing uppercase characters" do 95 | user = User.new 96 | User.stub(:new => user) 97 | invoke_cucumber_step('there is a User with the Name "Susanne"') 98 | user.name.should == "Susanne" 99 | end 100 | 101 | it "should override attr_accessible protection" do 102 | invoke_cucumber_step('there is a payment with the amount "120" and the comment "Thanks for lending"') 103 | payment = Payment.last 104 | payment.amount.should == 120 105 | payment.comment.should == 'Thanks for lending' 106 | end 107 | 108 | it "should allow to set an explicit primary key" do 109 | invoke_cucumber_step('there is a payment with the ID 2') 110 | payment = Payment.last 111 | payment.id.should == 2 112 | end 113 | 114 | it "should allow to name records and set a belongs_to association to that record by referring to that name" do 115 | invoke_cucumber_step('"Some Prequel" is a movie with the title "Before Sunrise"') 116 | invoke_cucumber_step('there is a movie with the title "Limitless"') 117 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"') 118 | movie = Movie.find_by_title!('Before Sunset') 119 | prequel = Movie.find_by_title!('Before Sunrise') 120 | movie.prequel.should == prequel 121 | end 122 | 123 | it "should allow to set a belongs_to association to a previously created record by referring to any string attribute of that record" do 124 | invoke_cucumber_step('there is a movie with the title "Before Sunrise"') 125 | invoke_cucumber_step('there is a movie with the title "Limitless"') 126 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Before Sunrise"') 127 | movie = Movie.find_by_title!('Before Sunset') 128 | prequel = Movie.find_by_title!('Before Sunrise') 129 | movie.prequel.should == prequel 130 | end 131 | 132 | it "should allow to set a belongs_to association to a previously updated record by referring to any string attribute used when updating" do 133 | invoke_cucumber_step('there is a movie') 134 | invoke_cucumber_step('the movie above has the title "Before Sunrise"') 135 | invoke_cucumber_step('there is a movie with the title "Limitless"') 136 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Before Sunrise"') 137 | movie = Movie.find_by_title!('Before Sunset') 138 | prequel = Movie.find_by_title!('Before Sunrise') 139 | movie.prequel.should == prequel 140 | end 141 | 142 | it "should allow to set a belongs_to association to a previously created record by referring to their explicitely set primary keys" do 143 | invoke_cucumber_step('there is a movie with the ID 123') 144 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel 123') 145 | movie = Movie.find_by_title!('Before Sunset') 146 | prequel = Movie.find(123) 147 | movie.prequel.should == prequel 148 | end 149 | 150 | it "should allow to set a belongs_to association to a previously created record by saying 'above'" do 151 | invoke_cucumber_step('there is a user with the name "Jane"') 152 | invoke_cucumber_step('there is a user with the name "John"') 153 | invoke_cucumber_step('there is a movie with the title "Limitless"') 154 | invoke_cucumber_step('there is a movie with the title "Before Sunrise"') 155 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above and the prequel above') 156 | before_sunset = Movie.find_by_title!("Before Sunset") 157 | before_sunset.prequel.title.should == "Before Sunrise" 158 | before_sunset.reviewer.name.should == "John" 159 | end 160 | 161 | if TRANSFORMS_SUPPORTED 162 | it "should fallback to using transforms when no named record is found" do 163 | user = User.create!(:name => 'Me') 164 | @main.instance_eval do 165 | Transform(/^(me)$/) do |value| 166 | user 167 | end 168 | end 169 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer "me"') 170 | before_sunset = Movie.find_by_title!("Before Sunset") 171 | before_sunset.reviewer.should == user 172 | end 173 | end 174 | 175 | it "should give created_at precedence over id when saying 'above' if the primary key is not numeric" do 176 | invoke_cucumber_step('there is a uuid user with the name "Jane" and the id "jane"') 177 | invoke_cucumber_step('there is a uuid user with the name "John" and the id "john"') 178 | 179 | uuid_user = UuidUser.find_by_name("John") 180 | ActiveRecord::VERSION::MAJOR >= 6 ? uuid_user.update!(:created_at => 1.day.ago) : uuid_user.update_attributes!(:created_at => 1.day.ago) 181 | 182 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the uuid reviewer above') 183 | before_sunset = Movie.find_by_title!("Before Sunset") 184 | before_sunset.uuid_reviewer.name.should == "Jane" 185 | end 186 | 187 | it "should ignore created_at if the primary key is numeric" do 188 | invoke_cucumber_step('there is a user with the name "Jane"') 189 | invoke_cucumber_step('there is a user with the name "John"') 190 | 191 | user = User.find_by_name("John") 192 | ActiveRecord::VERSION::MAJOR >= 6 ? user.update!(:created_at => 1.day.ago) : user.update_attributes!(:created_at => 1.day.ago) 193 | 194 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above') 195 | before_sunset = Movie.find_by_title!("Before Sunset") 196 | before_sunset.reviewer.name.should == "John" 197 | end 198 | 199 | it "should raise a proper error if there is no previous record when saying 'above'" do 200 | lambda do 201 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above and the prequel above') 202 | end.should raise_error(/There is no last reviewer/i) 203 | end 204 | 205 | it "should reload an object assigned to a belongs_to before assigning" do 206 | invoke_cucumber_step('"Jane" is a user who is deleted') 207 | 208 | user = User.last 209 | ActiveRecord::VERSION::MAJOR >= 6 ? user.update!(:deleted => false) : user.update_attributes!(:deleted => false) 210 | 211 | proc { invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer "Jane"') }.should_not raise_error 212 | end 213 | 214 | it "should allow to set positive boolean attributes with 'who' after the attribute list" do 215 | user = User.new 216 | User.stub(:new => user) 217 | invoke_cucumber_step('there is a user with the name "Jane" who is deleted') 218 | user.name.should == "Jane" 219 | user.deleted.should == true 220 | end 221 | 222 | it "should allow to name records and set a belongs_to association to that record by referring to that name" do 223 | invoke_cucumber_step('"Some Prequel" is a movie with the title "Before Sunrise"') 224 | invoke_cucumber_step('there is a movie with the title "Limitless"') 225 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"') 226 | movie = Movie.find_by_title!('Before Sunset') 227 | prequel = Movie.find_by_title!('Before Sunrise') 228 | movie.prequel.should == prequel 229 | end 230 | 231 | it "should allow to set positive boolean attributes with 'which' after the attribute list" do 232 | user = User.new 233 | User.stub(:new => user) 234 | invoke_cucumber_step('there is a user with the name "Jane" which is deleted') 235 | user.name.should == "Jane" 236 | user.deleted.should == true 237 | end 238 | 239 | it "should allow to set positive boolean attributes with 'that' after the attribute list" do 240 | user = User.new 241 | User.stub(:new => user) 242 | invoke_cucumber_step('there is a user with the name "Jane" that is deleted') 243 | user.name.should == "Jane" 244 | user.deleted.should == true 245 | end 246 | 247 | it "should allow to set boolean attributes without regular attributes preceding them" do 248 | user = User.new 249 | User.stub(:new => user) 250 | invoke_cucumber_step('there is a user who is deleted') 251 | user.deleted.should == true 252 | end 253 | 254 | it "should allow to set negative boolean attribute" do 255 | user = User.new 256 | User.stub(:new => user) 257 | invoke_cucumber_step('there is a user who is not deleted') 258 | user.deleted.should == false 259 | end 260 | 261 | it "should allow to set multiple boolean attributes" do 262 | user = User.new 263 | User.stub(:new => user) 264 | invoke_cucumber_step('there is a user who is locked and not deleted and subscribed') 265 | user.locked.should == true 266 | user.deleted.should == false 267 | user.subscribed.should == true 268 | end 269 | 270 | it "should allow to set boolean attributes that are named from multiple words" do 271 | user = User.new 272 | User.stub(:new => user) 273 | invoke_cucumber_step('there is a user who is locked and not scared and scared by spiders and deleted') 274 | user.locked.should == true 275 | user.scared.should == false 276 | user.scared_by_spiders.should == true 277 | user.deleted.should == true 278 | end 279 | 280 | it "should allow to join boolean attribute lists with 'and's, commas and 'but's" do 281 | user = User.new 282 | User.stub(:new => user) 283 | invoke_cucumber_step('there is a user who is locked, scared, but scared by spiders and deleted') 284 | user.locked.should == true 285 | user.scared.should == true 286 | user.scared_by_spiders.should == true 287 | user.deleted.should == true 288 | end 289 | 290 | it "should allow to set a has_many association by referring to multiple named records in square brackets" do 291 | invoke_cucumber_step('there is a movie with the title "Sunshine"') 292 | invoke_cucumber_step('there is a movie with the title "Limitless"') 293 | invoke_cucumber_step('there is a user with the reviewed movies ["Sunshine" and "Limitless"]') 294 | user = User.last 295 | reviewed_movie_titles = user.reviewed_movies.map(&:title) 296 | reviewed_movie_titles.should =~ ['Sunshine', 'Limitless'] 297 | end 298 | 299 | it 'allow associations for transient attributes if they are named after the associated model' do 300 | invoke_cucumber_step('there is a movie with the title "Sunshine"') 301 | invoke_cucumber_step('there is a user with the movie "Sunshine"') 302 | user = User.last 303 | user.reviewed_movies.count.should == 1 304 | user.reviewed_movies.first.title.should == 'Sunshine' 305 | end 306 | 307 | it 'allow associations for transient attributes if they are named like a factory' do 308 | invoke_cucumber_step('there is a movie with the title "Sunshine"') 309 | invoke_cucumber_step('there is a user with the film "Sunshine"') 310 | user = User.last 311 | user.reviewed_movies.count.should == 1 312 | user.reviewed_movies.first.title.should == 'Sunshine' 313 | end 314 | 315 | it "should allow to set attributes via doc string" do 316 | user = User.new 317 | User.stub(:new => user) 318 | 319 | invoke_cucumber_step('there is a user with these attributes:', <<-DOC_STRING) 320 | name: Jane 321 | locked: true 322 | DOC_STRING 323 | 324 | user.name.should == "Jane" 325 | user.locked.should == true 326 | end 327 | 328 | it "should allow to set attributes via additional doc string" do 329 | user = User.new 330 | User.stub(:new => user) 331 | 332 | invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', <<-DOC_STRING) 333 | name: Jane 334 | DOC_STRING 335 | 336 | user.name.should == "Jane" 337 | user.email.should == "test@invalid.com" 338 | end 339 | 340 | it 'should allow named records when setting attributes via doc string' do 341 | invoke_cucumber_step('"Some Prequel" is a movie with these attributes:', <<-DOC_STRING) 342 | title: Before Sunrise 343 | DOC_STRING 344 | invoke_cucumber_step('there is a movie with the title "Limitless"') 345 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"') 346 | movie = Movie.find_by_title!('Before Sunset') 347 | prequel = Movie.find_by_title!('Before Sunrise') 348 | movie.prequel.should == prequel 349 | end 350 | 351 | it "should allow to set attributes via data table" do 352 | user = User.new 353 | User.stub(:new => user) 354 | 355 | invoke_cucumber_step('there is a user with these attributes:', nil, <<-DATA_TABLE) 356 | | name | Jane | 357 | | locked | true | 358 | DATA_TABLE 359 | 360 | user.name.should == "Jane" 361 | user.locked.should == true 362 | end 363 | 364 | it "should allow to set attributes via additional data table" do 365 | user = User.new 366 | User.stub(:new => user) 367 | 368 | invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', nil, <<-DATA_TABLE) 369 | | name | Jane | 370 | DATA_TABLE 371 | 372 | user.name.should == "Jane" 373 | user.email.should == "test@invalid.com" 374 | end 375 | 376 | it 'should allow named records when setting attributes via data table' do 377 | invoke_cucumber_step('"Some Prequel" is a movie with these attributes:', nil, <<-DATA_TABLE) 378 | | title | Before Sunrise | 379 | DATA_TABLE 380 | invoke_cucumber_step('there is a movie with the title "Limitless"') 381 | invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"') 382 | movie = Movie.find_by_title!('Before Sunset') 383 | prequel = Movie.find_by_title!('Before Sunrise') 384 | movie.prequel.should == prequel 385 | end 386 | 387 | it "should allow mixed single quotes for model names" do 388 | invoke_cucumber_step("'Some Prequel' is a movie with the title \"Before Sunrise\"") 389 | invoke_cucumber_step('there is a movie with the title "Limitless"') 390 | invoke_cucumber_step('there is a movie with the title \'Before Sunset\' and the prequel "Some Prequel"') 391 | movie = Movie.find_by_title!('Before Sunset') 392 | prequel = Movie.find_by_title!('Before Sunrise') 393 | movie.prequel.should == prequel 394 | end 395 | 396 | it 'supports named associations with polymorphic associations' do 397 | invoke_cucumber_step('"my opera" is an opera') 398 | invoke_cucumber_step('there is a movie with the premiere site "my opera"') 399 | end 400 | 401 | it 'does not support last record references with polymorphic associations as the target class cannot be guessed' do 402 | invoke_cucumber_step('there is an opera') 403 | expect { 404 | invoke_cucumber_step('there is a movie with the premiere site above') 405 | }.to raise_error(CucumberFactory::Factory::Error, 'Cannot set last Movie#premiere_site for polymorphic associations') 406 | end 407 | 408 | it 'supports carrierwave uploaders' do 409 | invoke_cucumber_step('there is a payment with the attachment file:"spec/assets/file.txt"') 410 | payment = Payment.last 411 | expect(payment.attachment.file.read).to eq "This is a test file.\n" 412 | end 413 | 414 | it 'supports single quote for carrierwave uploaders' do 415 | invoke_cucumber_step("there is a payment with the attachment file:'spec/assets/file.txt'") 416 | payment = Payment.last 417 | expect(payment.attachment.file.read).to eq "This is a test file.\n" 418 | end 419 | 420 | it 'is able to read symlinked files' do 421 | invoke_cucumber_step("there is a payment with the attachment file:'spec/assets/symlink.txt'") 422 | payment = Payment.last 423 | expect(payment.attachment.file.read).to eq "This is a test file.\n" 424 | end 425 | 426 | it 'supports table syntax for carrierwave uploaders' do 427 | invoke_cucumber_step('there is a payment with these attributes:', nil, <<-DATA_TABLE) 428 | | attachment | file:"spec/assets/file.txt" | 429 | DATA_TABLE 430 | payment = Payment.last 431 | expect(payment.attachment.file.read).to eq "This is a test file.\n" 432 | end 433 | 434 | it 'supports doc string syntax for carrierwave uploaders' do 435 | invoke_cucumber_step('there is a payment with these attributes:', <<-DOC_STRING) 436 | attachment: file:"spec/assets/file.txt" 437 | DOC_STRING 438 | payment = Payment.last 439 | payment.save! 440 | expect(payment.attachment.file.read).to eq "This is a test file.\n" 441 | end 442 | 443 | it 'allows updating a carrierwave attribute' do 444 | invoke_cucumber_step('there is a payment with the attachment file:"spec/assets/file.txt"') 445 | payment = Payment.last 446 | expect(payment.attachment.file.read).to eq "This is a test file.\n" 447 | invoke_cucumber_step('the payment above has the attachment file:"spec/assets/file2.txt"') 448 | payment.reload 449 | expect(payment.attachment.file.read).to eq "This is the second test file.\n" 450 | end 451 | 452 | it 'works with nested factories (BUGFIX)' do 453 | invoke_cucumber_step('there is a subgenre movie') 454 | end 455 | 456 | describe 'references to named records are tracked correctly' do 457 | # CucumberFactory keeps track of all created records by their attributes. 458 | # For example, if you create a user with the name "Peter" you can reference him later on as "Peter". 459 | 460 | it 'does not overwrite existing references when associating new records with another reference (BUGFIX)' do 461 | invoke_cucumber_step('"reviewer" is a user with the id "1"') 462 | movie1 = invoke_cucumber_step('there is a movie with the reviewer "reviewer" and the id "2"') # <- should not overwrite the 'reviewer' reference 463 | movie2 = invoke_cucumber_step('there is a movie with the reviewer "reviewer" and the id "3"') 464 | 465 | expect(movie1.reviewer_id).to eq(1) 466 | expect(movie2.reviewer_id).to eq(1) 467 | end 468 | 469 | it 'does not set new references when setting primary keys of an association (BUGFIX)' do 470 | movie1 = invoke_cucumber_step('there is a movie with the id "123" and the user id "456"') # <- should not set a '456' reference 471 | movie2 = invoke_cucumber_step('there is a movie with the user id "456"') 472 | 473 | expect(movie1.reviewer_id).to eq(456) 474 | expect(movie2.reviewer_id).to eq(456) 475 | end 476 | end 477 | end 478 | 479 | context 'without FactoryBot' do 480 | before do 481 | hide_const("FactoryBot") 482 | end 483 | 484 | it "should instantiate plain ruby classes by calling #new" do 485 | PlainRubyClass.should_receive(:new).with({}) 486 | invoke_cucumber_step("there is a plain ruby class") 487 | end 488 | 489 | it "should instantiate namespaced classes" do 490 | actor = People::Actor.new 491 | People::Actor.should_receive(:new).and_return(actor) 492 | invoke_cucumber_step("there is a people/actor") 493 | end 494 | 495 | it "should allow to set integer attributes without surrounding quotes" do 496 | invoke_cucumber_step('there is a plain Ruby class with the amount 123 and the total 456') 497 | obj = PlainRubyClass.last 498 | obj.attributes[:amount].should == 123 499 | obj.attributes[:total].should == 456 500 | end 501 | 502 | it "should allow to set decimal attributes without surrounding quotes" do 503 | invoke_cucumber_step('there is a plain Ruby class with the amount 1.23 and the total 45.6') 504 | obj = PlainRubyClass.last 505 | obj.attributes[:amount].should be_a(BigDecimal) 506 | obj.attributes[:amount].to_s.should == "1.23" 507 | obj.attributes[:total].should be_a(BigDecimal) 508 | obj.attributes[:total].to_s.should == "45.6" 509 | end 510 | 511 | it "should allow to set file attributes with the file:'path' syntax" do 512 | invoke_cucumber_step('there is a plain Ruby class with the file file:"spec/assets/file.txt"') 513 | obj = PlainRubyClass.last 514 | obj.attributes[:file].should be_a(File) 515 | obj.attributes[:file].read.should == "This is a test file.\n" 516 | end 517 | 518 | it "should allow set an array of strings with square brackets" do 519 | invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar"] and the list ["bam", "baz"]') 520 | obj = PlainRubyClass.last 521 | obj.attributes[:tags].should == ['foo', 'bar'] 522 | obj.attributes[:list].should == ['bam', 'baz'] 523 | end 524 | 525 | it "should allow set an array of numbers with square brackets" do 526 | invoke_cucumber_step('there is a plain Ruby class with the integers [1, 2] and the decimals [3.4, 4.5]') 527 | obj = PlainRubyClass.last 528 | obj.attributes[:integers].should == [1, 2] 529 | obj.attributes[:decimals].should == [BigDecimal('3.4'), BigDecimal('4.5')] 530 | end 531 | 532 | it 'should allow to set an empty array' do 533 | invoke_cucumber_step('there is a plain Ruby class with the tags []') 534 | obj = PlainRubyClass.last 535 | obj.attributes[:tags].should == [] 536 | end 537 | 538 | it 'should allow to separate array values with either a comma or "and"' do 539 | invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar" and "baz"] and the list ["bam", "baz" and "qux"]') 540 | obj = PlainRubyClass.last 541 | obj.attributes[:tags].should == ['foo', 'bar', 'baz'] 542 | obj.attributes[:list].should == ['bam', 'baz', 'qux'] 543 | end 544 | 545 | it 'should allow to separate array values with an Oxford comma' do 546 | invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar", and "baz"] and the list ["bam", "baz", and "qux"]') 547 | obj = PlainRubyClass.last 548 | obj.attributes[:tags].should == ['foo', 'bar', 'baz'] 549 | obj.attributes[:list].should == ['bam', 'baz', 'qux'] 550 | end 551 | 552 | it "should allow attribute names starting with 'the'" do 553 | PlainRubyClass.should_receive(:new).with({:theme => 'Sci-fi'}) 554 | invoke_cucumber_step('there is a plain ruby class with the theme "Sci-fi"') 555 | end 556 | 557 | it "should allow attribute names starting with 'and'" do 558 | PlainRubyClass.should_receive(:new).with({:android => 'Paranoid'}) 559 | invoke_cucumber_step('there is a plain ruby class with the android "Paranoid"') 560 | end 561 | 562 | it "should allow attribute names starting with 'with'" do 563 | PlainRubyClass.should_receive(:new).with({:withdrawal => 'bank_account'}) 564 | invoke_cucumber_step('there is a plain ruby class with the withdrawal "bank_account"') 565 | end 566 | 567 | it "should allow attribute names starting with 'but'" do 568 | PlainRubyClass.should_receive(:new).with({:butt => 'pear-shaped'}) 569 | invoke_cucumber_step('there is a plain ruby class with the butt "pear-shaped"') 570 | end 571 | 572 | it "should allow to set array attributes via doc string" do 573 | invoke_cucumber_step('there is a plain Ruby class with these attributes:', <<-DOC_STRING) 574 | tags: ["foo", "bar"] 575 | DOC_STRING 576 | 577 | obj = PlainRubyClass.last 578 | obj.attributes[:tags].should == ['foo', 'bar'] 579 | end 580 | 581 | it "should allow to set array attributes via data table" do 582 | invoke_cucumber_step('there is a plain Ruby class with these attributes:', nil, <<-DATA_TABLE) 583 | | tags | ["foo", "bar"] | 584 | DATA_TABLE 585 | 586 | obj = PlainRubyClass.last 587 | obj.attributes[:tags].should == ['foo', 'bar'] 588 | end 589 | 590 | it "should create models that have a machinist blueprint by calling #make" do 591 | MachinistModel.should_receive(:make).with({ :attribute => "foo"}) 592 | invoke_cucumber_step('there is a machinist model with the attribute "foo"') 593 | end 594 | 595 | it "should be able to step_match machinist blueprint variants" do 596 | MachinistModel.should_receive(:make).with(:variant, { :attribute => "foo"}) 597 | invoke_cucumber_step('there is a machinist model (variant) with the attribute "foo"') 598 | end 599 | 600 | it "should be able to step_match machinist blueprint variants containing spaces or uppercase characters in prose" do 601 | MachinistModel.should_receive(:make).with(:variant_mark_two, { :attribute => "foo"}) 602 | invoke_cucumber_step('there is a machinist model (Variant Mark Two) with the attribute "foo"') 603 | end 604 | 605 | it "should allow single quote for attribute values" do 606 | MachinistModel.should_receive(:make).with({ :attribute => "foo"}) 607 | invoke_cucumber_step("there is a machinist model with the attribute 'foo'") 608 | end 609 | 610 | it "should allow mixed single and double quotes for different attribute values" do 611 | MachinistModel.should_receive(:make).with({ :attribute => "foo", :other_attribute => "bar" }) 612 | invoke_cucumber_step("there is a machinist model with the attribute 'foo' and the other attribute \"bar\"") 613 | end 614 | 615 | it 'should not raise an error for a blank instance name' do 616 | MachinistModel.should_receive(:make).with({ :attribute => 'foo' }) 617 | invoke_cucumber_step("'' is a machinist model with the attribute 'foo'") 618 | end 619 | 620 | it 'should warn if there are unused fragments' do 621 | MachinistModel.should_not_receive(:make) 622 | lambda { invoke_cucumber_step("there is a machinist model with the attribute NOQUOTES") }.should raise_error(ArgumentError, 'Unable to parse attributes " with the attribute NOQUOTES".') 623 | lambda { invoke_cucumber_step("there is a machinist model with the attribute 'foo' and the ") }.should raise_error(ArgumentError, 'Unable to parse attributes " and the ".') 624 | lambda { invoke_cucumber_step("there is a machinist model with the attribute 'foo'. the other_attribute 'bar' and the third attribute 'baz'") }.should raise_error(ArgumentError, 'Unable to parse attributes ".".') 625 | end 626 | 627 | it "should allow to update the last record of a type" do 628 | invoke_cucumber_step('there is a user with the name "Bar" and the email "foo@example.com" who is not subscribed') 629 | invoke_cucumber_step('the user above has the name "Baz", the email "foobar@example.com", and is subscribed') 630 | user = User.last 631 | user.name.should == 'Baz' 632 | user.email.should == 'foobar@example.com' 633 | user.subscribed.should == true 634 | end 635 | 636 | it "should be able to update a record with a given name" do 637 | invoke_cucumber_step('"Foo" is a user with the name "Bar"') 638 | invoke_cucumber_step('the user "Foo" has the email "foo@example.com"') 639 | invoke_cucumber_step("the user 'Foo' is subscribed") 640 | user = User.last 641 | user.name.should == 'Bar' 642 | user.email.should == 'foo@example.com' 643 | user.subscribed.should == true 644 | end 645 | 646 | it "should be able to update a record with a Cucumber table expression" do 647 | invoke_cucumber_step('"Foo" is a user with the name "Bar"') 648 | invoke_cucumber_step('the user above has these attributes:', nil, <<-DATA_TABLE) 649 | | name | Jane | 650 | | locked | true | 651 | DATA_TABLE 652 | user = User.last 653 | user.name.should == "Jane" 654 | user.locked.should == true 655 | end 656 | 657 | it "should allow to update a record via additional data table" do 658 | invoke_cucumber_step('"Foo" is a user with the name "Bar"') 659 | invoke_cucumber_step('the user above has the email "test@invalid.com", is subscribed, and has these attributes:', nil, <<-DATA_TABLE) 660 | | name | Jane | 661 | DATA_TABLE 662 | user = User.last 663 | user.name.should == "Jane" 664 | user.email.should == "test@invalid.com" 665 | user.subscribed.should == true 666 | end 667 | 668 | it "should allow to update an association" do 669 | invoke_cucumber_step('there is a movie with the title "Before Sunrise"') 670 | invoke_cucumber_step('there is a user with the name "John"') 671 | invoke_cucumber_step('the movie above has the reviewer above') 672 | movie = Movie.last 673 | movie.reviewer.name.should == "John" 674 | end 675 | end 676 | end 677 | --------------------------------------------------------------------------------