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.
Files changed (60) hide show
  1. data/.gitignore +2 -0
  2. data/History.txt +165 -0
  3. data/License.txt +20 -0
  4. data/README.rdoc +205 -0
  5. data/Rakefile +116 -0
  6. data/Todo.txt +4 -0
  7. data/VERSION +1 -0
  8. data/features/app/app.rb +112 -0
  9. data/features/app/blueprints.rb +11 -0
  10. data/features/app/factories.rb +23 -0
  11. data/features/app/views/notifier/email.erb +1 -0
  12. data/features/app/views/notifier/user_email.erb +6 -0
  13. data/features/email/email.feature +38 -0
  14. data/features/generator/generators.feature +54 -0
  15. data/features/path/models_page.feature +44 -0
  16. data/features/path/named_route_page.feature +10 -0
  17. data/features/pickle/create_from_active_record.feature +20 -0
  18. data/features/pickle/create_from_factory_girl.feature +43 -0
  19. data/features/pickle/create_from_machinist.feature +38 -0
  20. data/features/step_definitions/email_steps.rb +50 -0
  21. data/features/step_definitions/extra_email_steps.rb +7 -0
  22. data/features/step_definitions/fork_steps.rb +4 -0
  23. data/features/step_definitions/generator_steps.rb +42 -0
  24. data/features/step_definitions/path_steps.rb +14 -0
  25. data/features/step_definitions/pickle_steps.rb +41 -0
  26. data/features/support/env.rb +34 -0
  27. data/features/support/paths.rb +46 -0
  28. data/garlic.rb +36 -0
  29. data/init.rb +0 -0
  30. data/lib/pickle.rb +26 -0
  31. data/lib/pickle/adapter.rb +87 -0
  32. data/lib/pickle/config.rb +48 -0
  33. data/lib/pickle/email.rb +36 -0
  34. data/lib/pickle/email/parser.rb +18 -0
  35. data/lib/pickle/email/world.rb +13 -0
  36. data/lib/pickle/parser.rb +65 -0
  37. data/lib/pickle/parser/matchers.rb +87 -0
  38. data/lib/pickle/path.rb +44 -0
  39. data/lib/pickle/path/world.rb +5 -0
  40. data/lib/pickle/session.rb +151 -0
  41. data/lib/pickle/session/parser.rb +24 -0
  42. data/lib/pickle/version.rb +6 -0
  43. data/lib/pickle/world.rb +9 -0
  44. data/pickle.gemspec +107 -0
  45. data/rails_generators/pickle/pickle_generator.rb +41 -0
  46. data/rails_generators/pickle/templates/email_steps.rb +50 -0
  47. data/rails_generators/pickle/templates/env.rb +14 -0
  48. data/rails_generators/pickle/templates/paths.rb +20 -0
  49. data/rails_generators/pickle/templates/pickle_steps.rb +41 -0
  50. data/spec/lib/pickle_adapter_spec.rb +164 -0
  51. data/spec/lib/pickle_config_spec.rb +97 -0
  52. data/spec/lib/pickle_email_parser_spec.rb +49 -0
  53. data/spec/lib/pickle_email_spec.rb +131 -0
  54. data/spec/lib/pickle_parser_matchers_spec.rb +70 -0
  55. data/spec/lib/pickle_parser_spec.rb +154 -0
  56. data/spec/lib/pickle_path_spec.rb +77 -0
  57. data/spec/lib/pickle_session_spec.rb +337 -0
  58. data/spec/lib/pickle_spec.rb +24 -0
  59. data/spec/spec_helper.rb +38 -0
  60. metadata +122 -0
@@ -0,0 +1,7 @@
1
+ Given(/^an email "(.*?)" with body: "(.*?)" is delivered to (.+?)$/) do |subject, body, to|
2
+ Notifier.deliver_email(to, subject, body)
3
+ end
4
+
5
+ Given(/^#{capture_model}'s email is delivered$/) do |model|
6
+ Notifier.deliver_user_email(model(model))
7
+ end
@@ -0,0 +1,4 @@
1
+ # example of making your own matcher with the pickle backend
2
+ Then(/^#{capture_model} should be tine of #{capture_model}$/) do |tine, fork|
3
+ model(fork).tines.should include(model(tine))
4
+ end
@@ -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)
@@ -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
@@ -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
@@ -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