git-approvals 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -16,14 +16,31 @@ Or install it yourself as:
16
16
 
17
17
  $ gem install git-approvals
18
18
 
19
+ ## Background
20
+
21
+ [Approval tests][approvaltests] are a testing device popularized by Llewellyn Falco. The basic premise of an approval test is that the subject under test is captured and written to a file, thereby "approving" it. The test assertion is literally a diff of the approved file (expected) with the subject under test (actual). If there is a diff, the test fails because the result does not match the approved file.
22
+
23
+ For a more comprehensive background and use cases, see [this blog post][blog] I've written on the subject of approval tests.
24
+
25
+ **Git::Approvals** leverages `git` as a backend to make approval testing simple and the workflow familiar. Your project doesn't necessarily have to be version controlled by `git` to work, but the approved files must. This library assumes a basic working knowledge of how to use `git`.
26
+
19
27
  ## Usage
20
28
 
21
- > TODO describe standalone usage
29
+ ### Standalone
30
+
31
+ You can use approval tests manually in Ruby if you wish. Suppose you have a file named `foo/bar.txt` that contains the string "baz". The `Approval#diff` method takes a string and a block. It will shell out to `git` and will diff the given string with the file and will call the block if there is a diff or an error occurs.
32
+
33
+ ``` ruby
34
+ approval = Git::Approvals::Approval.new 'foo/bar.txt'
35
+ approval.diff( "baz" ){ |err| puts "fail" } # nothing
36
+ approval.diff( "qux" ){ |err| puts "fail" } # "fail"
37
+ ```
38
+
39
+ The constructor method also accepts a hash of options, which are explained below.
22
40
 
23
- ### With RSpec
41
+ ### RSpec Integration
24
42
 
25
- In your spec_helper.rb or wherever, simply `require 'rspec/approvals'`.
26
- Now you can use the `verify` expectation in your specs:
43
+ In your `spec_helper.rb` or wherever, simply `require 'rspec/approvals'`. You can now use the `verify` expectation in your specs:
27
44
 
28
45
  `spec/foo_spec.rb`
29
46
 
@@ -48,6 +65,26 @@ Another test run shows that the test now passes. On the next test run, the
48
65
  same file will be written out. If git says the file has changed, the
49
66
  test will fail.
50
67
 
68
+ The `verify` method accepts the same options hash as the Approval constructor.
69
+
70
+ ### Options
71
+
72
+ The following options are supported:
73
+
74
+ - `:format`: specifies the format to use when writing out the approved file. The format is inferred from the extension of the approved file, but if you specify it here, it will change the extension to match. This option accepts any extension that is registered with [tilt][tilt].
75
+ - `:filename`: specifies the filename of the approved file.
76
+
77
+ ### Formatters
78
+
79
+ **Git::Approvals** uses [tilt][tilt] to write out approved files, so any template format that tilt supports can be used. **Git::Approvals** adds the following formats in addition to the standard tilt templates:
80
+
81
+ - `txt` writes out a ruby object using [awesome_print][awesomeprint].
82
+ - `json` pretty-prints a json string using the standard JSON library.
83
+ - `js` pretty-prints JavaScript source using [uglifier][uglifier].
84
+ - `css` pretty-prints CSS source using [SASS][sass].
85
+
86
+ The format will be determined by checking the `:format` key of the options hash, then the extension of the approved filename, and finally will default to `txt`.
87
+
51
88
  ## Contributing
52
89
 
53
90
  1. Fork it
@@ -55,3 +92,10 @@ test will fail.
55
92
  3. Commit your changes (`git commit -am 'Add some feature'`)
56
93
  4. Push to the branch (`git push origin my-new-feature`)
57
94
  5. Create new Pull Request
95
+
96
+ [blog]: http://jeremyruppel.tumblr.com/post/52734828127/committed-for-your-approval
97
+ [tilt]: https://github.com/rtomayko/tilt
98
+ [approvaltests]: http://approvaltests.sourceforge.net/
99
+ [awesomeprint]: https://github.com/michaeldv/awesome_print
100
+ [uglifier]: https://github.com/lautis/uglifier
101
+ [sass]: https://github.com/nex3/sass
@@ -1,5 +1,4 @@
1
1
  require 'open3'
2
- require 'pathname'
3
2
  require 'tilt'
4
3
 
5
4
  module Git
@@ -7,40 +6,13 @@ module Git
7
6
 
8
7
  ##
9
8
  #
10
- class Approval < Pathname
9
+ class Approval
10
+ include Git::Approvals::Utils
11
11
 
12
12
  def initialize( path, options={} ) # :nodoc:
13
- @options = options
14
-
15
- path = Pathname( path )
16
- path = munge_format( path )
17
- path = munge_filename( path )
18
-
19
- super path.to_path
20
- end
21
- attr_reader :options
22
-
23
- ##
24
- # Makes sure the given pathname has an extension.
25
- def munge_format( path )
26
- ext = [ options[ :format ], path.extname, 'txt' ].detect do |arg|
27
- !arg.nil? && !arg.empty?
28
- end
29
- ext = ext.to_s
30
- ext = '.' + ext unless ext.start_with?( '.' )
31
- path.sub_ext ext
32
- end
33
-
34
- ##
35
- # Makes sure the given pathname has the correct filename.
36
- def munge_filename( path )
37
- if filename = options[ :filename ]
38
- dir, base = path.split
39
- dir.join( filename + base.extname )
40
- else
41
- path
42
- end
13
+ @path = transform_filename( path, options )
43
14
  end
15
+ attr_reader :path
44
16
 
45
17
  ##
46
18
  # Diffs the given string with this approval file. If the
@@ -49,20 +21,20 @@ module Git
49
21
  # called if the diff fails, meaning there are differences.
50
22
  def diff( string, &block )
51
23
  # Make sure the directory of the file exists.
52
- dirname.mkpath
24
+ FileUtils.mkdir_p File.dirname( path )
53
25
 
54
26
  # Write the new string to the file.
55
- open 'w' do |f|
56
- f << Tilt.new( to_path ).render( string )
27
+ File.open path, 'w' do |f|
28
+ f << Tilt.new( path ).render( string )
57
29
  end
58
30
 
59
31
  # If the file hasn't been checked in, raise an error.
60
- sh "git ls-files #{to_path} --error-unmatch" do |err|
61
- raise Errno::ENOENT, to_path
32
+ sh "git ls-files #{path} --error-unmatch" do |err|
33
+ raise Errno::ENOENT, path
62
34
  end
63
35
 
64
36
  # If the file has changed, call the block.
65
- sh "git diff --exit-code #{to_path}" do |err|
37
+ sh "git diff --exit-code #{path}" do |err|
66
38
  block.call err
67
39
  end
68
40
  end
@@ -79,6 +51,7 @@ module Git
79
51
 
80
52
  ##
81
53
  # Register all formatters as tilt templates.
54
+ Tilt.register PlainFormatter, ''
82
55
  Tilt.register AwesomePrintFormatter, 'txt'
83
56
  Tilt.register JSONFormatter, 'json'
84
57
  Tilt.register UglifierFormatter, 'js'
@@ -0,0 +1,19 @@
1
+ require 'tilt'
2
+
3
+ module Git
4
+ module Approvals
5
+ class PlainFormatter < Tilt::Template
6
+
7
+ def self.engine_initialized?
8
+ true
9
+ end
10
+
11
+ def prepare
12
+ end
13
+
14
+ def evaluate( context, locals, &block )
15
+ context.to_s
16
+ end
17
+ end
18
+ end
19
+ end
@@ -12,6 +12,24 @@ module Git
12
12
  .downcase
13
13
  end
14
14
  module_function :filenamify
15
+
16
+ ##
17
+ #
18
+ def transform_filename( str, options={} )
19
+
20
+ if opt = options.delete( :format )
21
+ str.chomp! File.extname( str )
22
+ str << '.' << opt.to_s
23
+ end
24
+
25
+ if opt = options.delete( :filename )
26
+ base = File.basename( str, File.extname( str ) )
27
+ str.sub! /#{base}(?!\/)/, opt
28
+ end
29
+
30
+ str
31
+ end
32
+ module_function :transform_filename
15
33
  end
16
34
  end
17
35
  end
@@ -1,5 +1,5 @@
1
1
  module Git
2
2
  module Approvals
3
- VERSION = '0.3.0'
3
+ VERSION = '0.4.0'
4
4
  end
5
5
  end
data/lib/git/approvals.rb CHANGED
@@ -5,6 +5,7 @@ module Git
5
5
  autoload :Approval, 'git/approvals/approval'
6
6
  autoload :AwesomePrintFormatter, 'git/approvals/awesome_print_formatter.rb'
7
7
  autoload :JSONFormatter, 'git/approvals/json_formatter.rb'
8
+ autoload :PlainFormatter, 'git/approvals/plain_formatter.rb'
8
9
  autoload :SassFormatter, 'git/approvals/sass_formatter.rb'
9
10
  autoload :UglifierFormatter, 'git/approvals/uglifier_formatter.rb'
10
11
  autoload :Utils, 'git/approvals/utils'
@@ -0,0 +1 @@
1
+ Plain text.
@@ -0,0 +1,2 @@
1
+ /* Comments are preserved */
2
+ html,body{font-family:"Helvetica Neue";}
@@ -0,0 +1,2 @@
1
+ // Comments are preserved
2
+ (function(){return {status:"IT WERKS"};})();
@@ -0,0 +1 @@
1
+ {"foo":"bar","baz":"quux"}
@@ -0,0 +1 @@
1
+ Plain text.
@@ -1,3 +1,4 @@
1
+ /* Comments are preserved */
1
2
  html, body {
2
3
  font-family: "Helvetica Neue";
3
4
  }
File without changes
File without changes
@@ -0,0 +1,8 @@
1
+ {
2
+ :foo => "bar",
3
+ :baz => [
4
+ [0] 1,
5
+ [1] 2,
6
+ [2] 3
7
+ ]
8
+ }
@@ -3,24 +3,13 @@ require 'spec_helper'
3
3
  describe Git::Approvals::Approval do
4
4
 
5
5
  describe '#initialize' do
6
- subject { described_class.new './foo/bar.txt' }
7
-
8
- its( :to_path ){ should == './foo/bar.txt' }
9
- its( :options ){ should == { } }
10
- end
11
-
12
- describe '#options' do
13
- describe ':format' do
14
- it 'replaces the extension' do
15
- subject = described_class.new 'foo/bar.txt', :format => :json
16
- subject.to_path.should == 'foo/bar.json'
17
- end
6
+ context 'straight up' do
7
+ subject { described_class.new 'foo/bar.txt' }
8
+ its( :path ){ should == 'foo/bar.txt' }
18
9
  end
19
- describe ':filename' do
20
- it 'replaces the filename' do
21
- subject = described_class.new 'foo/bar.txt', :filename => 'baz'
22
- subject.to_path.should == 'foo/baz.txt'
23
- end
10
+ context 'with options' do
11
+ subject { described_class.new 'foo/bar.txt', :format => :json, :filename => 'baz' }
12
+ its( :path ){ should == 'foo/baz.json' }
24
13
  end
25
14
  end
26
15
 
@@ -56,36 +45,12 @@ describe Git::Approvals::Approval do
56
45
  end
57
46
  end
58
47
 
59
- describe 'formats' do
60
- it 'formats strings' do
61
- approval = described_class.new './spec/fixtures/string.txt'
62
- approval.diff( 'IT WERKS' ){ |diff| fail diff }
63
- end
64
- it 'formats arrays' do
65
- approval = described_class.new './spec/fixtures/array.txt'
66
- approval.diff( [ :foo, :bar, :baz ] ){ |diff| fail diff }
67
- end
68
- it 'formats hashes' do
69
- approval = described_class.new './spec/fixtures/hash.txt'
70
- approval.diff( { :foo => 'bar', :quux => 'bar' } ){ |diff| fail diff }
71
- end
72
- it 'formats json' do
73
- approval = described_class.new './spec/fixtures/hash.json'
74
- approval.diff( '{"foo":"bar","baz":"quux"}' ){ |diff| fail diff }
75
- end
76
- it 'formats javascript' do
77
- approval = described_class.new './spec/fixtures/asset.js'
78
- approval.diff( <<-EOS ){ |diff| fail diff }
79
- // Comments are preserved
80
- (function(){return {status:"IT WERKS"};})();
81
- EOS
82
- end
83
- it 'formats css' do
84
- approval = described_class.new './spec/fixtures/asset.css'
85
- approval.diff( <<-EOS ){ |diff| fail diff }
86
- // Comments are preserved
87
- html,body{font-family:"Helvetica Neue";}
88
- EOS
89
- end
48
+ describe 'Tilt mappings' do
49
+ subject { Tilt.mappings }
50
+ it { should include( '' => [ Git::Approvals::PlainFormatter ] ) }
51
+ it { should include( 'txt' => [ Git::Approvals::AwesomePrintFormatter ] ) }
52
+ it { should include( 'css' => [ Git::Approvals::SassFormatter ] ) }
53
+ it { should include( 'json' => [ Git::Approvals::JSONFormatter ] ) }
54
+ it { should include( 'js' => [ Git::Approvals::UglifierFormatter ] ) }
90
55
  end
91
56
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'formatter' do |ext|
4
+ let( :input ){ File.read "./spec/fixtures/input#{ext}" }
5
+ let( :output ){ "./spec/fixtures/output#{ext}" }
6
+
7
+ it 'formats the input appropriately' do
8
+ approval = Git::Approvals::Approval.new output
9
+ approval.diff( input ){ |diff| fail diff }
10
+ end
11
+ end
12
+
13
+ describe Git::Approvals::PlainFormatter do
14
+ include_examples 'formatter', ''
15
+ end
16
+ describe Git::Approvals::JSONFormatter do
17
+ include_examples 'formatter', '.json'
18
+ end
19
+ describe Git::Approvals::UglifierFormatter do
20
+ include_examples 'formatter', '.js'
21
+ end
22
+ describe Git::Approvals::SassFormatter do
23
+ include_examples 'formatter', '.css'
24
+ end
25
+ describe Git::Approvals::AwesomePrintFormatter do
26
+ include_examples 'formatter', '.txt' do
27
+ let( :input ){ { :foo => 'bar', :baz => [ 1, 2, 3 ] } }
28
+ end
29
+ end
@@ -18,7 +18,7 @@ describe 'RSpec integration' do
18
18
  after { `git checkout #{approval_directory}` }
19
19
 
20
20
  it 'passes when unchanged' do
21
- verify { 'IT WERKS' }
21
+ expect { verify { 'IT WERKS' } }.not_to raise_error
22
22
  end
23
23
  it 'fails when changed' do
24
24
  expect { verify { 'IT BROKE' } }.to raise_error( RSpec::Expectations::ExpectationNotMetError )
@@ -15,4 +15,20 @@ describe Git::Approvals::Utils do
15
15
  end
16
16
  end
17
17
  end
18
+
19
+ describe '#transform_filename' do
20
+ {
21
+ [ 'foo' ] => 'foo',
22
+ [ 'foo', { :format => :json } ] => 'foo.json',
23
+ [ 'foo', { :format => 'json' } ] => 'foo.json',
24
+ [ 'foo.txt', { :format => :json } ] => 'foo.json',
25
+ [ 'foo', { :filename => 'bar' } ] => 'bar',
26
+ [ 'foo.txt', { :filename => 'bar' } ] => 'bar.txt',
27
+ [ 'foo/foo.txt', { :filename => 'bar' } ] => 'foo/bar.txt'
28
+ }.each do |args, filename|
29
+ example 'with %s' % [ args.inspect ] do
30
+ described_class.transform_filename( *args ).should == filename
31
+ end
32
+ end
33
+ end
18
34
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-approvals
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-14 00:00:00.000000000 Z
12
+ date: 2013-06-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: tilt
@@ -194,22 +194,27 @@ files:
194
194
  - lib/git/approvals/approval.rb
195
195
  - lib/git/approvals/awesome_print_formatter.rb
196
196
  - lib/git/approvals/json_formatter.rb
197
+ - lib/git/approvals/plain_formatter.rb
197
198
  - lib/git/approvals/rspec.rb
198
199
  - lib/git/approvals/sass_formatter.rb
199
200
  - lib/git/approvals/uglifier_formatter.rb
200
201
  - lib/git/approvals/utils.rb
201
202
  - lib/git/approvals/version.rb
202
203
  - lib/rspec/approvals.rb
203
- - spec/fixtures/array.txt
204
- - spec/fixtures/asset.css
205
- - spec/fixtures/asset.js
206
- - spec/fixtures/hash.json
207
- - spec/fixtures/hash.txt
208
- - spec/fixtures/string.txt
204
+ - spec/fixtures/input
205
+ - spec/fixtures/input.css
206
+ - spec/fixtures/input.js
207
+ - spec/fixtures/input.json
208
+ - spec/fixtures/output
209
+ - spec/fixtures/output.css
210
+ - spec/fixtures/output.js
211
+ - spec/fixtures/output.json
212
+ - spec/fixtures/output.txt
209
213
  - spec/git/approvals/approval_spec.rb
214
+ - spec/git/approvals/formatter_spec.rb
210
215
  - spec/git/approvals/rspec_spec.rb
211
- - spec/git/approvals/rspec_spec/rspec_integration/verify/fails_when_changed.txt
212
- - spec/git/approvals/rspec_spec/rspec_integration/verify/passes_when_unchanged.txt
216
+ - spec/git/approvals/rspec_spec/rspec_integration/verify/fails_when_changed
217
+ - spec/git/approvals/rspec_spec/rspec_integration/verify/passes_when_unchanged
213
218
  - spec/git/approvals/utils_spec.rb
214
219
  - spec/spec_helper.rb
215
220
  homepage: https://github.com/jeremyruppel/git-approvals
@@ -238,16 +243,20 @@ signing_key:
238
243
  specification_version: 3
239
244
  summary: Simple git-powered approval tests.
240
245
  test_files:
241
- - spec/fixtures/array.txt
242
- - spec/fixtures/asset.css
243
- - spec/fixtures/asset.js
244
- - spec/fixtures/hash.json
245
- - spec/fixtures/hash.txt
246
- - spec/fixtures/string.txt
246
+ - spec/fixtures/input
247
+ - spec/fixtures/input.css
248
+ - spec/fixtures/input.js
249
+ - spec/fixtures/input.json
250
+ - spec/fixtures/output
251
+ - spec/fixtures/output.css
252
+ - spec/fixtures/output.js
253
+ - spec/fixtures/output.json
254
+ - spec/fixtures/output.txt
247
255
  - spec/git/approvals/approval_spec.rb
256
+ - spec/git/approvals/formatter_spec.rb
248
257
  - spec/git/approvals/rspec_spec.rb
249
- - spec/git/approvals/rspec_spec/rspec_integration/verify/fails_when_changed.txt
250
- - spec/git/approvals/rspec_spec/rspec_integration/verify/passes_when_unchanged.txt
258
+ - spec/git/approvals/rspec_spec/rspec_integration/verify/fails_when_changed
259
+ - spec/git/approvals/rspec_spec/rspec_integration/verify/passes_when_unchanged
251
260
  - spec/git/approvals/utils_spec.rb
252
261
  - spec/spec_helper.rb
253
262
  has_rdoc:
@@ -1,5 +0,0 @@
1
- [
2
- [0] :foo,
3
- [1] :bar,
4
- [2] :baz
5
- ]
@@ -1,4 +0,0 @@
1
- {
2
- :foo => "bar",
3
- :quux => "bar"
4
- }
@@ -1 +0,0 @@
1
- "IT WERKS"