pickle 0.1.16
Sign up to get free protection for your applications and to get access to all the features.
- 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
|