Indicative docstrings #71

Closed
wants to merge 9 commits into
from
+372 −163
Split
View
25 features/custom_matchers/define_matcher.feature
@@ -8,6 +8,7 @@ Feature: define matcher
Given a file named "matcher_with_default_message_spec.rb" with:
"""
require 'rspec/expectations'
+ RSpec.configuration.generated_docstring_format = :indicative
RSpec::Matchers.define :be_a_multiple_of do |expected|
match do |actual|
@@ -37,8 +38,8 @@ Feature: define matcher
When I run `rspec ./matcher_with_default_message_spec.rb --format documentation`
Then the exit status should not be 0
- And the output should contain "should be a multiple of 3"
- And the output should contain "should not be a multiple of 4"
+ And the output should contain "is a multiple of 3"
+ And the output should contain "is not a multiple of 4"
And the output should contain "Failure/Error: it {should be_a_multiple_of(4)}"
And the output should contain "Failure/Error: it {should_not be_a_multiple_of(3)}"
@@ -94,17 +95,21 @@ Feature: define matcher
And the stdout should contain "1 example, 1 failure"
And the stdout should contain "expected that 9 would not be a multiple of 3"
- Scenario: overriding the description
+ Scenario: overriding the docstrings
Given a file named "matcher_overriding_description_spec.rb" with:
"""
require 'rspec/expectations'
+ RSpec.configuration.generated_docstring_format = :indicative
RSpec::Matchers.define :be_a_multiple_of do |expected|
match do |actual|
actual % expected == 0
end
- description do
- "be multiple of #{expected}"
+ docstring_for_should do
+ "is multiple of #{expected}"
+ end
+ docstring_for_should_not do
+ "is not multiple of #{expected}"
end
end
@@ -119,13 +124,14 @@ Feature: define matcher
When I run `rspec ./matcher_overriding_description_spec.rb --format documentation`
Then the exit status should be 0
And the stdout should contain "2 examples, 0 failures"
- And the stdout should contain "should be multiple of 3"
- And the stdout should contain "should not be multiple of 4"
+ And the stdout should contain "is multiple of 3"
+ And the stdout should contain "is not multiple of 4"
Scenario: with no args
Given a file named "matcher_with_no_args_spec.rb" with:
"""
require 'rspec/expectations'
+ RSpec.configuration.generated_docstring_format = :indicative
RSpec::Matchers.define :have_7_fingers do
match do |thing|
@@ -144,12 +150,13 @@ Feature: define matcher
When I run `rspec ./matcher_with_no_args_spec.rb --format documentation`
Then the exit status should be 0
And the stdout should contain "1 example, 0 failures"
- And the stdout should contain "should have 7 fingers"
+ And the stdout should contain "has 7 fingers"
Scenario: with multiple args
Given a file named "matcher_with_multiple_args_spec.rb" with:
"""
require 'rspec/expectations'
+ RSpec.configuration.generated_docstring_format = :indicative
RSpec::Matchers.define :be_the_sum_of do |a,b,c,d|
match do |sum|
@@ -164,7 +171,7 @@ Feature: define matcher
When I run `rspec ./matcher_with_multiple_args_spec.rb --format documentation`
Then the exit status should be 0
And the stdout should contain "1 example, 0 failures"
- And the stdout should contain "should be the sum of 1, 2, 3, and 4"
+ And the stdout should contain "is the sum of 1, 2, 3, and 4"
Scenario: with helper methods
Given a file named "matcher_with_internal_helper_spec.rb" with:
View
6 features/custom_matchers/define_matcher_with_fluent_interface.feature
@@ -5,6 +5,10 @@ Feature: define matcher with fluent interface
Scenario: chained method with argumetn
Given a file named "between_spec.rb" with:
"""
+ require 'rspec/expectations'
+
+ RSpec.configuration.generated_docstring_format = :indicative
+
RSpec::Matchers.define :be_bigger_than do |first|
match do |actual|
(actual > first) && (actual < @second)
@@ -21,4 +25,4 @@ Feature: define matcher with fluent interface
"""
When I run `rspec between_spec.rb --format documentation`
Then the output should contain "1 example, 0 failures"
- And the output should contain "should be bigger than 4"
+ And the output should contain "is bigger than 4"
View
29 features/implicit_docstrings.feature
@@ -50,3 +50,32 @@ Feature: implicit docstrings
And the output should contain "should be > 5"
And the output should contain "should include 4"
And the output should contain "should not respond to #size"
+
+ Scenario: run failing examples (indicative docstrings enabled)
+ Given a file named "failing_implicit_docstrings_spec.rb" with:
+ """
+ require 'rspec/expectations'
+ RSpec.configuration.generated_docstring_format = :indicative
+
+ describe "Failing examples with no descriptions" do
+
+ # description is auto-generated as "should equal(5)" based on the last #should
+ it do
+ 3.should equal(2)
+ 5.should equal(5)
+ end
+
+ it { 3.should be > 5 }
+
+ it { [1,2,3].should include(4) }
+
+ it { [1,2,3].should_not respond_to(:size) }
+
+ end
+ """
+
+ When I run `rspec ./failing_implicit_docstrings_spec.rb -fdoc`
+ Then the output should contain "equals 2"
+ And the output should contain "is > 5"
+ And the output should contain "includes 4"
+ And the output should contain "does not respond to #size"
View
2 lib/rspec/expectations.rb
@@ -1,3 +1,4 @@
+require 'rspec/core'
require 'rspec/expectations/extensions'
require 'rspec/matchers'
require 'rspec/expectations/fail_with'
@@ -35,5 +36,6 @@ module RSpec
# RSpec ships with a standard set of useful matchers, and writing your own
# matchers is quite simple. See RSpec::Matchers for details.
module Expectations
+ RSpec.configuration.add_setting :generated_docstring_format, :default => :modal
end
end
View
6 lib/rspec/matchers.rb
@@ -94,6 +94,12 @@ module RSpec
# description do
# # generate and return the appropriate string.
# end
+ # docstring_for_should do
+ # # generate and return the appropriate string.
+ # end
+ # docstring_for_should_not do
+ # # generate and return the appropriate string.
+ # end
# end
#
# Each of the message-generation methods has access to the block arguments
View
24 lib/rspec/matchers/be.rb
@@ -53,6 +53,14 @@ def description
"be"
end
+ def docstring_for_should
+ "is"
+ end
+
+ def docstring_for_should_not
+ "is not"
+ end
+
[:==, :<, :<=, :>=, :>, :===].each do |operator|
define_method operator do |operand|
BeComparedTo.new(operand, operator)
@@ -114,6 +122,14 @@ def description
"be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
end
+ def docstring_for_should
+ "is #{@operator} #{expected_to_sentence}#{args_to_sentence}"
+ end
+
+ def docstring_for_should_not
+ "is not #{@operator} #{expected_to_sentence}#{args_to_sentence}"
+ end
+
end
class BePredicate < Be
@@ -151,6 +167,14 @@ def description
"#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
end
+ def docstring_for_should
+ "is #{expected_to_sentence}#{args_to_sentence}"
+ end
+
+ def docstring_for_should_not
+ "is not #{expected_to_sentence}#{args_to_sentence}"
+ end
+
private
def predicate
View
4 lib/rspec/matchers/eq.rb
@@ -43,6 +43,10 @@ def eq(expected)
description do
"== #{_expected_}"
end
+
+ docstring_for_should do
+ "== #{_expected_}"
+ end
end
end
end
View
8 lib/rspec/matchers/equal.rb
@@ -47,6 +47,14 @@ def inspect_object(o)
MESSAGE
end
+
+ docstring_for_should do
+ "equals #{_expected_.inspect}"
+ end
+
+ docstring_for_should_not do
+ "does not equal #{_expected_.inspect}"
+ end
end
end
end
View
26 lib/rspec/matchers/generated_descriptions.rb
@@ -11,13 +11,22 @@ def self.clear_generated_description
def self.generated_description
return nil if last_should.nil?
- "#{last_should.to_s.gsub('_',' ')} #{last_description}"
+ negative = last_should.to_s =~ /not/
+ last_description(negative)
end
private
-
- def self.last_description
- last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE
+
+ def self.indicative_docstring(negative=false)
+ if negative && last_matcher.respond_to?(:docstring_for_should_not)
+ last_matcher.docstring_for_should_not
+ elsif last_matcher.respond_to?(:docstring_for_should)
+ last_matcher.docstring_for_should
+ end
+ end
+
+ def self.modal_docstring
+ last_matcher.respond_to?(:description) ? "#{last_should.to_s.sub("_", " ")} #{last_matcher.description}" : <<-MESSAGE
When you call a matcher in an example without a String, like this:
specify { object.should matcher }
@@ -31,6 +40,13 @@ def self.last_description
description method. Then you won't have to suffer this lengthy warning again.
MESSAGE
end
+
+ def self.last_description(negative=false)
+ if RSpec.configuration.generated_docstring_format == :indicative
+ self.indicative_docstring(negative)
+ else
+ self.modal_docstring
+ end
+ end
end
end
-
View
8 lib/rspec/matchers/has.rb
@@ -21,6 +21,14 @@ def description
[method_description(@expected), args_description(@args)].compact.join(' ')
end
+ def docstring_for_should
+ description.sub(/have/, "has")
+ end
+
+ def docstring_for_should_not
+ "not #{description}"
+ end
+
private
def predicate(sym)
"#{sym.to_s.sub("have_","has_")}?".to_sym
View
8 lib/rspec/matchers/have.rb
@@ -66,6 +66,14 @@ def failure_message_for_should_not
def description
"have #{relative_expectation} #{@collection_name}"
end
+
+ def docstring_for_should
+ "has #{relative_expectation} #{@collection_name}"
+ end
+
+ def docstring_for_should_not
+ "does not have #{relative_expectation} #{@collection_name}"
+ end
def respond_to?(sym)
@expected.respond_to?(sym) || super
View
4 lib/rspec/matchers/match_array.rb
@@ -31,6 +31,10 @@ def description
"contain exactly #{_pretty_print(@expected)}"
end
+ def docstring_for_should
+ "contains exactly #{_pretty_print(@expected)}"
+ end
+
private
def safe_sort(array)
View
21 lib/rspec/matchers/matcher.rb
@@ -15,6 +15,8 @@ def initialize(name, *expected, &declarations)
@match_for_should_not_block = nil
@messages = {
+ :docstring_for_should => lambda { "#{name_to_indicative_sentence}#{expected_to_sentence}" },
+ :docstring_for_should_not => lambda { "#{name_to_indicative_sentence(true)}#{expected_to_sentence}" },
:description => lambda {"#{name_to_sentence}#{expected_to_sentence}"},
:failure_message_for_should => lambda {|actual| "expected #{actual.inspect} to #{name_to_sentence}#{expected_to_sentence}"},
:failure_message_for_should_not => lambda {|actual| "expected #{actual.inspect} not to #{name_to_sentence}#{expected_to_sentence}"}
@@ -91,6 +93,16 @@ def description(&block)
cache_or_call_cached(:description, &block)
end
+ # See RSpec::Matchers
+ def docstring_for_should(&block)
+ cache_or_call_cached(:docstring_for_should, &block)
+ end
+
+ # See RSpec::Matchers
+ def docstring_for_should_not(&block)
+ cache_or_call_cached(:docstring_for_should_not, &block)
+ end
+
#Used internally by objects returns by +should+ and +should_not+.
def diffable?
@diffable
@@ -151,6 +163,15 @@ def name_to_sentence
split_words(@name)
end
+ def name_to_indicative_sentence(negated=false)
+ if negated
+ indicative = to_indicative(name_to_sentence)
+ indicative =~ /is\s/ ? indicative.sub("is", "is not") : "does not #{name_to_sentence}"
+ else
+ to_indicative(name_to_sentence)
+ end
+ end
+
def expected_to_sentence
to_sentence(@expected)
end
View
8 lib/rspec/matchers/operator_matcher.rb
@@ -51,6 +51,14 @@ def fail_with_message(message)
def description
"#{@operator} #{@expected.inspect}"
end
+
+ def docstring_for_should
+ description
+ end
+
+ def docstring_for_should_not
+ "does not #{description}"
+ end
private
View
15 lib/rspec/matchers/pretty.rb
@@ -19,6 +19,19 @@ def to_sentence(words)
end
end
+ def to_indicative(infinitive)
+ changed = false
+ { /be/ => "is",
+ /have/ => "has",
+ /match/ => "matches"}.each_pair do |pattern, substitution|
+ if infinitive =~ pattern
+ infinitive.sub!(pattern, substitution)
+ changed = true
+ end
+ end
+ changed ? infinitive : infinitive.to_s + 's'
+ end
+
def _pretty_print(array)
result = ""
array.each_with_index do |item, index|
@@ -34,4 +47,4 @@ def _pretty_print(array)
end
end
end
-end
+end
View
8 lib/rspec/matchers/raise_error.rb
@@ -67,6 +67,14 @@ def failure_message_for_should_not
def description
"raise #{expected_error}"
end
+
+ def docstring_for_should
+ "raises #{expected_error}"
+ end
+
+ def docstring_for_should_not
+ "does not raise #{expected_error}"
+ end
private
def expected_error
View
8 lib/rspec/matchers/respond_to.rb
@@ -27,6 +27,14 @@ def description
"respond to #{pp_names}#{with_arity}"
end
+ def docstring_for_should
+ "responds to #{pp_names}#{with_arity}"
+ end
+
+ def docstring_for_should_not
+ "does not respond to #{pp_names}#{with_arity}"
+ end
+
def with(n)
@expected_arity = n
self
View
8 lib/rspec/matchers/throw_symbol.rb
@@ -63,6 +63,14 @@ def failure_message_for_should_not
def description
"throw #{expected}"
end
+
+ def docstring_for_should
+ "throws #{expected}"
+ end
+
+ def docstring_for_should_not
+ "does not throw #{expected}"
+ end
private
View
317 spec/rspec/matchers/description_generation_spec.rb
@@ -1,155 +1,178 @@
require 'spec_helper'
-describe "Matchers should be able to generate their own descriptions" do
+describe "Matchers generate their own descriptions" do
after(:each) do
RSpec::Matchers.clear_generated_description
end
-
- it "should == expected" do
- "this".should == "this"
- RSpec::Matchers.generated_description.should == "should == \"this\""
- end
-
- it "should not == expected" do
- "this".should_not == "that"
- RSpec::Matchers.generated_description.should == "should not == \"that\""
- end
-
- it "should be empty (arbitrary predicate)" do
- [].should be_empty
- RSpec::Matchers.generated_description.should == "should be empty"
- end
-
- it "should not be empty (arbitrary predicate)" do
- [1].should_not be_empty
- RSpec::Matchers.generated_description.should == "should not be empty"
- end
-
- it "should be true" do
- true.should be_true
- RSpec::Matchers.generated_description.should == "should be true"
- end
-
- it "should be false" do
- false.should be_false
- RSpec::Matchers.generated_description.should == "should be false"
- end
-
- it "should be nil" do
- nil.should be_nil
- RSpec::Matchers.generated_description.should == "should be nil"
- end
-
- it "should be > n" do
- 5.should be > 3
- RSpec::Matchers.generated_description.should == "should be > 3"
- end
-
- it "should be predicate arg1, arg2 and arg3" do
- 5.0.should be_between(0,10)
- RSpec::Matchers.generated_description.should == "should be between 0 and 10"
- end
-
- it "should equal" do
- expected = "expected"
- expected.should equal(expected)
- RSpec::Matchers.generated_description.should == "should equal \"expected\""
- end
-
- it "should_not equal" do
- 5.should_not equal(37)
- RSpec::Matchers.generated_description.should == "should not equal 37"
- end
-
- it "should eql" do
- "string".should eql("string")
- RSpec::Matchers.generated_description.should == "should eql \"string\""
- end
-
- it "should not eql" do
- "a".should_not eql(:a)
- RSpec::Matchers.generated_description.should == "should not eql :a"
- end
-
- it "should have_key" do
- {:a => "a"}.should have_key(:a)
- RSpec::Matchers.generated_description.should == "should have key :a"
- end
-
- it "should have_some_method" do
- object = Object.new
- def object.has_eyes_closed?; true; end
-
- object.should have_eyes_closed
- RSpec::Matchers.generated_description.should == 'should have eyes closed'
- end
-
- it "should have_some_method(args*)" do
- object = Object.new
- def object.has_taste_for?(*args); true; end
-
- object.should have_taste_for("wine", "cheese")
- RSpec::Matchers.generated_description.should == 'should have taste for "wine", "cheese"'
- end
-
- it "should have n items" do
- team.should have(3).players
- RSpec::Matchers.generated_description.should == "should have 3 players"
- end
-
- it "should have at least n items" do
- team.should have_at_least(2).players
- RSpec::Matchers.generated_description.should == "should have at least 2 players"
- end
-
- it "should have at most n items" do
- team.should have_at_most(4).players
- RSpec::Matchers.generated_description.should == "should have at most 4 players"
- end
-
- it "should include" do
- [1,2,3].should include(3)
- RSpec::Matchers.generated_description.should == "should include 3"
- end
-
- it "array.should =~ [1,2,3]" do
- [1,2,3].should =~ [1,2,3]
- RSpec::Matchers.generated_description.should == "should contain exactly 1, 2 and 3"
- end
-
- it "should match" do
- "this string".should match(/this string/)
- RSpec::Matchers.generated_description.should == "should match /this string/"
- end
-
- it "should raise_error" do
- lambda { raise }.should raise_error
- RSpec::Matchers.generated_description.should == "should raise Exception"
- end
-
- it "should raise_error with type" do
- lambda { raise }.should raise_error(RuntimeError)
- RSpec::Matchers.generated_description.should == "should raise RuntimeError"
- end
-
- it "should raise_error with type and message" do
- lambda { raise "there was an error" }.should raise_error(RuntimeError, "there was an error")
- RSpec::Matchers.generated_description.should == "should raise RuntimeError with \"there was an error\""
- end
-
- it "should respond_to" do
- [].should respond_to(:insert)
- RSpec::Matchers.generated_description.should == "should respond to #insert"
- end
-
- it "should throw symbol" do
- lambda { throw :what_a_mess }.should throw_symbol
- RSpec::Matchers.generated_description.should == "should throw a Symbol"
- end
-
- it "should throw symbol (with named symbol)" do
- lambda { throw :what_a_mess }.should throw_symbol(:what_a_mess)
- RSpec::Matchers.generated_description.should == "should throw :what_a_mess"
+
+ context "when the generated description format is indicative" do
+ before { RSpec::configuration.generated_docstring_format = :indicative }
+
+ it "should == expected" do
+ "this".should == "this"
+ RSpec::Matchers.generated_description.should == "== \"this\""
+ end
+
+ it "should not == expected" do
+ "this".should_not == "that"
+ RSpec::Matchers.generated_description.should == "does not == \"that\""
+ end
+
+ it "should be empty (arbitrary predicate)" do
+ [].should be_empty
+ RSpec::Matchers.generated_description.should == "is empty"
+ end
+
+ it "should not be empty (arbitrary predicate)" do
+ [1].should_not be_empty
+ RSpec::Matchers.generated_description.should == "is not empty"
+ end
+
+ it "should be true" do
+ true.should be_true
+ RSpec::Matchers.generated_description.should == "is true"
+ end
+
+ it "should be false" do
+ false.should be_false
+ RSpec::Matchers.generated_description.should == "is false"
+ end
+
+ it "should be nil" do
+ nil.should be_nil
+ RSpec::Matchers.generated_description.should == "is nil"
+ end
+
+ it "should be > n" do
+ 5.should be > 3
+ RSpec::Matchers.generated_description.should == "is > 3"
+ end
+
+ it "should be predicate arg1, arg2 and arg3" do
+ 5.0.should be_between(0,10)
+ RSpec::Matchers.generated_description.should == "is between 0 and 10"
+ end
+
+ it "should not be predicate arg1, arg2 and arg3" do
+ 5.0.should_not be_between(0, 1)
+ RSpec::Matchers.generated_description.should == "is not between 0 and 1"
+ end
+
+ it "should equal" do
+ expected = "expected"
+ expected.should equal(expected)
+ RSpec::Matchers.generated_description.should == "equals \"expected\""
+ end
+
+ it "should_not equal" do
+ 5.should_not equal(37)
+ RSpec::Matchers.generated_description.should == "does not equal 37"
+ end
+
+ it "should eql" do
+ "string".should eql("string")
+ RSpec::Matchers.generated_description.should == "eqls \"string\""
+ end
+
+ it "should not eql" do
+ "a".should_not eql(:a)
+ RSpec::Matchers.generated_description.should == "does not eql :a"
+ end
+
+ it "should have_key" do
+ {:a => "a"}.should have_key(:a)
+ RSpec::Matchers.generated_description.should == "has key :a"
+ end
+
+ it "should have_some_method" do
+ object = Object.new
+ def object.has_eyes_closed?; true; end
+
+ object.should have_eyes_closed
+ RSpec::Matchers.generated_description.should == 'has eyes closed'
+ end
+
+ it "should have_some_method(args*)" do
+ object = Object.new
+ def object.has_taste_for?(*args); true; end
+
+ object.should have_taste_for("wine", "cheese")
+ RSpec::Matchers.generated_description.should == 'has taste for "wine", "cheese"'
+ end
+
+ it "should have n items" do
+ team.should have(3).players
+ RSpec::Matchers.generated_description.should == "has 3 players"
+ end
+
+ it "should have at least n items" do
+ team.should have_at_least(2).players
+ RSpec::Matchers.generated_description.should == "has at least 2 players"
+ end
+
+ it "should have at most n items" do
+ team.should have_at_most(4).players
+ RSpec::Matchers.generated_description.should == "has at most 4 players"
+ end
+
+ it "should include" do
+ [1,2,3].should include(3)
+ RSpec::Matchers.generated_description.should == "includes 3"
+ end
+
+ it "array.should =~ [1,2,3]" do
+ [1,2,3].should =~ [1,2,3]
+ RSpec::Matchers.generated_description.should == "contains exactly 1, 2 and 3"
+ end
+
+ it "should match" do
+ "this string".should match(/this string/)
+ RSpec::Matchers.generated_description.should == "matches /this string/"
+ end
+
+ it "should raise_error" do
+ lambda { raise }.should raise_error
+ RSpec::Matchers.generated_description.should == "raises Exception"
+ end
+
+ it "should raise_error with type" do
+ lambda { raise }.should raise_error(RuntimeError)
+ RSpec::Matchers.generated_description.should == "raises RuntimeError"
+ end
+
+ it "should raise_error with type and message" do
+ lambda { raise "there was an error" }.should raise_error(RuntimeError, "there was an error")
+ RSpec::Matchers.generated_description.should == "raises RuntimeError with \"there was an error\""
+ end
+
+ it "should not raise_error with type and message" do
+ lambda { "Innocuous" }.should_not raise_error(RuntimeError, "there was an error")
+ RSpec::Matchers.generated_description.should == "does not raise RuntimeError with \"there was an error\""
+ end
+
+ it "should respond_to" do
+ [].should respond_to(:insert)
+ RSpec::Matchers.generated_description.should == "responds to #insert"
+ end
+
+ it "should throw symbol" do
+ lambda { throw :what_a_mess }.should throw_symbol
+ RSpec::Matchers.generated_description.should == "throws a Symbol"
+ end
+
+ it "should throw symbol (with named symbol)" do
+ lambda { throw :what_a_mess }.should throw_symbol(:what_a_mess)
+ RSpec::Matchers.generated_description.should == "throws :what_a_mess"
+ end
+ end
+
+ context "when the generated docstring format is modal" do
+ before { RSpec.configuration.generated_docstring_format = :modal }
+
+ it "should == expected" do
+ 3.should == 3
+ RSpec::Matchers.generated_description.should == "should == 3"
+ end
end
def team