pickle 0.1.16

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