pickle 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/History.txt +165 -0
- data/License.txt +20 -0
- data/README.rdoc +205 -0
- data/Rakefile +116 -0
- data/Todo.txt +4 -0
- data/VERSION +1 -0
- data/features/app/app.rb +112 -0
- data/features/app/blueprints.rb +11 -0
- data/features/app/factories.rb +23 -0
- data/features/app/views/notifier/email.erb +1 -0
- data/features/app/views/notifier/user_email.erb +6 -0
- data/features/email/email.feature +38 -0
- data/features/generator/generators.feature +54 -0
- data/features/path/models_page.feature +44 -0
- data/features/path/named_route_page.feature +10 -0
- data/features/pickle/create_from_active_record.feature +20 -0
- data/features/pickle/create_from_factory_girl.feature +43 -0
- data/features/pickle/create_from_machinist.feature +38 -0
- data/features/step_definitions/email_steps.rb +50 -0
- data/features/step_definitions/extra_email_steps.rb +7 -0
- data/features/step_definitions/fork_steps.rb +4 -0
- data/features/step_definitions/generator_steps.rb +42 -0
- data/features/step_definitions/path_steps.rb +14 -0
- data/features/step_definitions/pickle_steps.rb +41 -0
- data/features/support/env.rb +34 -0
- data/features/support/paths.rb +46 -0
- data/garlic.rb +36 -0
- data/init.rb +0 -0
- data/lib/pickle.rb +26 -0
- data/lib/pickle/adapter.rb +87 -0
- data/lib/pickle/config.rb +48 -0
- data/lib/pickle/email.rb +36 -0
- data/lib/pickle/email/parser.rb +18 -0
- data/lib/pickle/email/world.rb +13 -0
- data/lib/pickle/parser.rb +65 -0
- data/lib/pickle/parser/matchers.rb +87 -0
- data/lib/pickle/path.rb +44 -0
- data/lib/pickle/path/world.rb +5 -0
- data/lib/pickle/session.rb +151 -0
- data/lib/pickle/session/parser.rb +24 -0
- data/lib/pickle/version.rb +6 -0
- data/lib/pickle/world.rb +9 -0
- data/pickle.gemspec +107 -0
- data/rails_generators/pickle/pickle_generator.rb +41 -0
- data/rails_generators/pickle/templates/email_steps.rb +50 -0
- data/rails_generators/pickle/templates/env.rb +14 -0
- data/rails_generators/pickle/templates/paths.rb +20 -0
- data/rails_generators/pickle/templates/pickle_steps.rb +41 -0
- data/spec/lib/pickle_adapter_spec.rb +164 -0
- data/spec/lib/pickle_config_spec.rb +97 -0
- data/spec/lib/pickle_email_parser_spec.rb +49 -0
- data/spec/lib/pickle_email_spec.rb +131 -0
- data/spec/lib/pickle_parser_matchers_spec.rb +70 -0
- data/spec/lib/pickle_parser_spec.rb +154 -0
- data/spec/lib/pickle_path_spec.rb +77 -0
- data/spec/lib/pickle_session_spec.rb +337 -0
- data/spec/lib/pickle_spec.rb +24 -0
- data/spec/spec_helper.rb +38 -0
- metadata +122 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
Before do
|
2
|
+
`mv #{Rails.root}/features/ #{Rails.root}/features.orig/ > /dev/null 2>&1`
|
3
|
+
end
|
4
|
+
|
5
|
+
After do
|
6
|
+
`rm -rf #{Rails.root}/features`
|
7
|
+
`mv #{Rails.root}/features.orig/ #{Rails.root}/features/ > /dev/null 2>&1`
|
8
|
+
end
|
9
|
+
|
10
|
+
Given(/^cucumber has been freshly generated$/) do
|
11
|
+
`cd #{Rails.root}; script/generate cucumber -f`
|
12
|
+
end
|
13
|
+
|
14
|
+
Given(/^env\.rb already requires (.+)$/) do |file|
|
15
|
+
File.open("#{Rails.root}/features/support/env.rb", "a") do |env|
|
16
|
+
env << "require '#{file}'\n"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
When(/^I run "(.*)"$/) do |command|
|
21
|
+
@output = `cd #{Rails.root}; #{command}`
|
22
|
+
end
|
23
|
+
|
24
|
+
Then(/^I should see "(.*)"$/) do |text|
|
25
|
+
@output.should include(text)
|
26
|
+
end
|
27
|
+
|
28
|
+
Then(/^the file (.+?) should exist$/) do |file|
|
29
|
+
File.exist?("#{Rails.root}/#{file}").should == true
|
30
|
+
end
|
31
|
+
|
32
|
+
Then(/^the file (.+?) should match \/(.*?)\/$/) do |file, regexp|
|
33
|
+
File.read("#{Rails.root}/#{file}").should match(/#{regexp}/m)
|
34
|
+
end
|
35
|
+
|
36
|
+
Then(/^the file (.+?) should not match \/(.*?)\/$/) do |file, regexp|
|
37
|
+
File.read("#{Rails.root}/#{file}").should_not match(/#{regexp}/m)
|
38
|
+
end
|
39
|
+
|
40
|
+
Then /^the file features\/support\/paths\.rb should be identical to the local support\/paths\.rb$/ do
|
41
|
+
File.read("#{Rails.root}/features/support/paths.rb").should == File.read("#{File.dirname(__FILE__)}/../support/paths.rb")
|
42
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
|
2
|
+
|
3
|
+
Then(/^(.+?) should match route \/(.+?)$/) do |page, route|
|
4
|
+
regexp = route.gsub(/:(\w*?)id/,'\d+')
|
5
|
+
path_to(page).should =~ /#{regexp}/
|
6
|
+
end
|
7
|
+
|
8
|
+
When(/^I go to (.+)$/) do |page|
|
9
|
+
visit path_to(page)
|
10
|
+
end
|
11
|
+
|
12
|
+
Then(/^I should be at (.+)$/) do |page|
|
13
|
+
request.path.should =~ /#{path_to(page)}/
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# this file generated by script/generate pickle
|
2
|
+
|
3
|
+
# create a model
|
4
|
+
Given(/^#{capture_model} exists?(?: with #{capture_fields})?$/) do |name, fields|
|
5
|
+
create_model(name, fields)
|
6
|
+
end
|
7
|
+
|
8
|
+
# create n models
|
9
|
+
Given(/^(\d+) #{capture_plural_factory} exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
|
10
|
+
count.to_i.times { create_model(plural_factory.singularize, fields) }
|
11
|
+
end
|
12
|
+
|
13
|
+
# find a model
|
14
|
+
Then(/^#{capture_model} should exist(?: with #{capture_fields})?$/) do |name, fields|
|
15
|
+
find_model(name, fields).should_not be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# find exactly n models
|
19
|
+
Then(/^(\d+) #{capture_plural_factory} should exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
|
20
|
+
find_models(plural_factory.singularize, fields).size.should == count.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
# assert model is in another model's has_many assoc
|
24
|
+
Then(/^#{capture_model} should be (?:in|one of|amongst) #{capture_model}'s (\w+)$/) do |target, owner, association|
|
25
|
+
model(owner).send(association).should include(model(target))
|
26
|
+
end
|
27
|
+
|
28
|
+
# assert model is another model's has_one/belongs_to assoc
|
29
|
+
Then(/^#{capture_model} should be #{capture_model}'s (\w+)$/) do |target, owner, association|
|
30
|
+
model(owner).send(association).should == model(target)
|
31
|
+
end
|
32
|
+
|
33
|
+
# assert model.predicate?
|
34
|
+
Then(/^#{capture_model} should (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
|
35
|
+
model(name).should send("be_#{predicate.gsub(' ', '_')}")
|
36
|
+
end
|
37
|
+
|
38
|
+
# assert not model.predicate?
|
39
|
+
Then(/^#{capture_model} should not (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
|
40
|
+
model(name).should_not send("be_#{predicate.gsub(' ', '_')}")
|
41
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Sets up the Rails environment for Cucumber
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '../../../../../../config/environment')
|
4
|
+
require 'cucumber/rails/world'
|
5
|
+
|
6
|
+
require 'cucumber/formatter/unicode'
|
7
|
+
|
8
|
+
Cucumber::Rails.use_transactional_fixtures
|
9
|
+
|
10
|
+
require 'webrat'
|
11
|
+
|
12
|
+
Webrat.configure do |config|
|
13
|
+
config.mode = :rails
|
14
|
+
end
|
15
|
+
|
16
|
+
# Comment out the next line if you're not using RSpec's matchers (should / should_not) in your steps.
|
17
|
+
require 'cucumber/rails/rspec'
|
18
|
+
require 'webrat/core/matchers'
|
19
|
+
|
20
|
+
# Pickle
|
21
|
+
require 'pickle/world'
|
22
|
+
require 'pickle/path/world'
|
23
|
+
require 'pickle/email/world'
|
24
|
+
|
25
|
+
Pickle.configure do |c|
|
26
|
+
c.map 'I', :to => 'user: "me"'
|
27
|
+
c.map 'killah fork', :to => 'fancy fork: "of cornwood"'
|
28
|
+
end
|
29
|
+
|
30
|
+
# test app setup
|
31
|
+
__APP__ = File.expand_path(File.join(File.dirname(__FILE__), '../app'))
|
32
|
+
require "#{__APP__}/app"
|
33
|
+
require "#{__APP__}/factories"
|
34
|
+
require "#{__APP__}/blueprints"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module NavigationHelpers
|
2
|
+
# Maps a name to a path. Used by the
|
3
|
+
#
|
4
|
+
# When /^I go to (.+)$/ do |page_name|
|
5
|
+
#
|
6
|
+
# step definition in webrat_steps.rb
|
7
|
+
#
|
8
|
+
def path_to(page_name)
|
9
|
+
case page_name
|
10
|
+
|
11
|
+
when /the homepage/
|
12
|
+
'/'
|
13
|
+
|
14
|
+
# Add more mappings here.
|
15
|
+
# Here is a more fancy example:
|
16
|
+
#
|
17
|
+
# when /^(.*)'s profile page$/i
|
18
|
+
# user_profile_path(User.find_by_login($1))
|
19
|
+
|
20
|
+
# added by script/generate pickle path
|
21
|
+
|
22
|
+
when /^#{capture_model}(?:'s)? page$/ # eg. the forum's page
|
23
|
+
path_to_pickle $1
|
24
|
+
|
25
|
+
when /^#{capture_model}(?:'s)? #{capture_model}(?:'s)? page$/ # eg. the forum's post's page
|
26
|
+
path_to_pickle $1, $2
|
27
|
+
|
28
|
+
when /^#{capture_model}(?:'s)? #{capture_model}'s (.+?) page$/ # eg. the forum's post's comments page
|
29
|
+
path_to_pickle $1, $2, :extra => $3 # or the forum's post's edit page
|
30
|
+
|
31
|
+
when /^#{capture_model}(?:'s)? (.+?) page$/ # eg. the forum's posts page
|
32
|
+
path_to_pickle $1, :extra => $2 # or the forum's edit page
|
33
|
+
|
34
|
+
when /^the (.+?) page$/ # translate to named route
|
35
|
+
send "#{$1.downcase.gsub(' ','_')}_path"
|
36
|
+
|
37
|
+
# end added by pickle path
|
38
|
+
|
39
|
+
else
|
40
|
+
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
41
|
+
"Now, go and add a mapping in #{__FILE__}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
World(NavigationHelpers)
|
data/garlic.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
garlic do
|
2
|
+
repo 'rails', :url => 'git://github.com/rails/rails'
|
3
|
+
repo 'rspec', :url => 'git://github.com/dchelimsky/rspec'
|
4
|
+
repo 'rspec-rails', :url => 'git://github.com/dchelimsky/rspec-rails'
|
5
|
+
repo 'factory_girl', :url => 'git://github.com/thoughtbot/factory_girl'
|
6
|
+
repo 'machinist', :url => 'git://github.com/notahat/machinist'
|
7
|
+
repo 'cucumber', :url => 'git://github.com/aslakhellesoy/cucumber'
|
8
|
+
repo 'webrat', :url => 'git://github.com/brynary/webrat'
|
9
|
+
repo 'pickle', :path => '.'
|
10
|
+
|
11
|
+
['2-3-stable', '2-2-stable', '2-1-stable'].each do |rails|
|
12
|
+
|
13
|
+
target rails, :tree_ish => "origin/#{rails}" do
|
14
|
+
prepare do
|
15
|
+
plugin 'pickle', :clone => true
|
16
|
+
plugin 'rspec'
|
17
|
+
plugin 'rspec-rails' do
|
18
|
+
`script/generate rspec -f`
|
19
|
+
end
|
20
|
+
plugin 'factory_girl'
|
21
|
+
plugin 'cucumber' do
|
22
|
+
`script/generate cucumber -f`
|
23
|
+
end
|
24
|
+
plugin 'machinist'
|
25
|
+
plugin 'webrat'
|
26
|
+
end
|
27
|
+
|
28
|
+
run do
|
29
|
+
cd "vendor/plugins/pickle" do
|
30
|
+
sh "rake rcov:verify && rake features"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
data/init.rb
ADDED
File without changes
|
data/lib/pickle.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'pickle/version'
|
3
|
+
require 'pickle/adapter'
|
4
|
+
require 'pickle/config'
|
5
|
+
require 'pickle/parser'
|
6
|
+
require 'pickle/session'
|
7
|
+
require 'pickle/session/parser'
|
8
|
+
|
9
|
+
# make the parser aware of models in the session (for fields refering to models)
|
10
|
+
Pickle::Parser.send :include, Pickle::Session::Parser
|
11
|
+
|
12
|
+
module Pickle
|
13
|
+
class << self
|
14
|
+
def config
|
15
|
+
@config ||= Config.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def configure(&block)
|
19
|
+
config.configure(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def parser(options = {})
|
23
|
+
@parser ||= Parser.new({:config => config}.merge(options))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Pickle
|
2
|
+
# Abstract Factory adapter class, if you have a factory type setup, you
|
3
|
+
# can easily create an adaptor to make it work with Pickle.
|
4
|
+
#
|
5
|
+
# The factory adaptor must have a #factories class method that returns
|
6
|
+
# its instances, and each instance must respond to:
|
7
|
+
#
|
8
|
+
# #name : identifies the factory by name (default is attr_reader)
|
9
|
+
# #klass : returns the associated model class for this factory (default is attr_reader)
|
10
|
+
# #create(attrs = {}) : returns a newly created object
|
11
|
+
class Adapter
|
12
|
+
attr_reader :name, :klass
|
13
|
+
|
14
|
+
def self.factories
|
15
|
+
raise NotImplementedError, "return an array of factory adapter objects"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(attrs = {})
|
19
|
+
raise NotImplementedError, "create and return an object with the given attributes"
|
20
|
+
end
|
21
|
+
|
22
|
+
cattr_writer :model_classes
|
23
|
+
self.model_classes = nil
|
24
|
+
|
25
|
+
def self.model_classes
|
26
|
+
# remove abstract, framework, and non-table classes
|
27
|
+
@@model_classes ||= ::ActiveRecord::Base.send(:subclasses).reject do |klass|
|
28
|
+
klass.abstract_class? || !klass.table_exists? ||
|
29
|
+
(defined?(CGI::Session::ActiveRecordStore::Session) && klass == CGI::Session::ActiveRecordStore::Session) ||
|
30
|
+
(defined?(::ActiveRecord::SessionStore::Session) && klass == ::ActiveRecord::SessionStore::Session)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# machinist adapter
|
35
|
+
class Machinist < Adapter
|
36
|
+
def self.factories
|
37
|
+
factories = []
|
38
|
+
model_classes.each do |klass|
|
39
|
+
if blueprints = klass.instance_variable_get('@blueprints')
|
40
|
+
blueprints.keys.each {|blueprint| factories << new(klass, blueprint)}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
factories
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(klass, blueprint)
|
47
|
+
@klass, @blueprint = klass, blueprint
|
48
|
+
@name = @klass.name.underscore.gsub('/','_')
|
49
|
+
@name = "#{@blueprint}_#{@name}" unless @blueprint == :master
|
50
|
+
end
|
51
|
+
|
52
|
+
def create(attrs = {})
|
53
|
+
@klass.send(:make, @blueprint, attrs)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# factory-girl adapter
|
58
|
+
class FactoryGirl < Adapter
|
59
|
+
def self.factories
|
60
|
+
(::Factory.factories.values rescue []).map {|factory| new(factory)}
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(factory)
|
64
|
+
@klass, @name = factory.build_class, factory.factory_name.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
def create(attrs = {})
|
68
|
+
Factory.create(@name, attrs)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# fallback active record adapter
|
73
|
+
class ActiveRecord < Adapter
|
74
|
+
def self.factories
|
75
|
+
model_classes.map {|klass| new(klass) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def initialize(klass)
|
79
|
+
@klass, @name = klass, klass.name.underscore.gsub('/','_')
|
80
|
+
end
|
81
|
+
|
82
|
+
def create(attrs = {})
|
83
|
+
@klass.send(:create!, attrs)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Pickle
|
4
|
+
class Config
|
5
|
+
attr_writer :adapters, :factories, :mappings, :predicates
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
configure(&block) if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure(&block)
|
12
|
+
yield(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def adapters
|
16
|
+
@adapters ||= [:machinist, :factory_girl, :active_record]
|
17
|
+
end
|
18
|
+
|
19
|
+
def adapter_classes
|
20
|
+
adapters.map {|a| a.is_a?(Class) ? a : "pickle/adapter/#{a}".classify.constantize}
|
21
|
+
end
|
22
|
+
|
23
|
+
def factories
|
24
|
+
@factories ||= adapter_classes.reverse.inject({}) do |factories, adapter|
|
25
|
+
factories.merge(adapter.factories.inject({}){|h, f| h.merge(f.name => f)})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def predicates
|
30
|
+
@predicates ||= Pickle::Adapter.model_classes.map do |k|
|
31
|
+
k.public_instance_methods.select{|m| m =~ /\?$/} + k.column_names
|
32
|
+
end.flatten.uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
def mappings
|
36
|
+
@mappings ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Usage: map 'me', 'myself', 'I', :to => 'user: "me"'
|
40
|
+
def map(*args)
|
41
|
+
options = args.extract_options!
|
42
|
+
raise ArgumentError, "Usage: map 'search' [, 'search2', ...] :to => 'replace'" unless args.any? && options[:to].is_a?(String)
|
43
|
+
args.each do |search|
|
44
|
+
self.mappings << OpenStruct.new(:search => search, :replacement => options[:to])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/pickle/email.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Pickle
|
2
|
+
module Email
|
3
|
+
# return the deliveries array, optionally selected by the passed fields
|
4
|
+
def emails(fields = nil)
|
5
|
+
@emails = ActionMailer::Base.deliveries.select {|m| email_has_fields?(m, fields)}
|
6
|
+
end
|
7
|
+
|
8
|
+
def email(ref, fields = nil)
|
9
|
+
(match = ref.match(/^#{capture_index_in_email}$/)) or raise ArgumentError, "argument should match #{match_email}"
|
10
|
+
@emails or raise RuntimeError, "Call #emails before calling #email"
|
11
|
+
index = parse_index(match[1])
|
12
|
+
email_has_fields?(@emails[index], fields) ? @emails[index] : nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def email_has_fields?(email, fields)
|
16
|
+
parse_fields(fields).each do |key, val|
|
17
|
+
return false unless (Array(email.send(key)) & Array(val)).any?
|
18
|
+
end
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
# Saves the emails out to RAILS_ROOT/tmp/ and opens it in the default
|
24
|
+
# web browser if on OS X. (depends on webrat)
|
25
|
+
def save_and_open_emails
|
26
|
+
emails_to_open = @emails || emails
|
27
|
+
filename = "#{RAILS_ROOT}/tmp/webrat-email-#{Time.now.to_i}.html"
|
28
|
+
File.open(filename, "w") do |f|
|
29
|
+
emails_to_open.each_with_index do |e, i|
|
30
|
+
f.write "<h1>Email #{i+1}</h1><pre>#{e}</pre><hr />"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
open_in_browser(filename)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Pickle
|
2
|
+
module Email
|
3
|
+
# add ability to parse emails
|
4
|
+
module Parser
|
5
|
+
def match_email
|
6
|
+
"(?:#{match_prefix}?(?:#{match_index} )?email)"
|
7
|
+
end
|
8
|
+
|
9
|
+
def capture_email
|
10
|
+
"(#{match_email})"
|
11
|
+
end
|
12
|
+
|
13
|
+
def capture_index_in_email
|
14
|
+
"(?:#{match_prefix}?(?:#{capture_index} )?email)"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|