butternut 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -23,6 +23,7 @@ end
23
23
 
24
24
  require 'spec/rake/spectask'
25
25
  Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.ruby_opts = %w{-X+O}
26
27
  spec.libs << 'lib' << 'spec'
27
28
  spec.spec_files = FileList['spec/**/*_spec.rb']
28
29
  end
@@ -56,6 +57,6 @@ namespace :tmp do
56
57
  desc 'Delete temporary files'
57
58
  task :clear do
58
59
  require 'fileutils'
59
- FileUtils.rm_rf(Dir.glob(File.dirname(__FILE__) + "/tmp/features/*"), :verbose => true)
60
+ FileUtils.rm_rf(Dir.glob(File.dirname(__FILE__) + "/tmp/*"))
60
61
  end
61
62
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
data/butternut.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{butternut}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jeremy Stephens"]
12
- s.date = %q{2009-11-10}
12
+ s.date = %q{2009-12-14}
13
13
  s.description = %q{Based on Cucumber's HTML formatter, Butternut uses Celerity to capture page sources after each step.}
14
14
  s.email = %q{viking415@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -41,8 +41,7 @@ Gem::Specification.new do |s|
41
41
  "spec/fixtures/picard.jpg",
42
42
  "spec/spec.opts",
43
43
  "spec/spec_helper.rb",
44
- "tmp/features/.gitignore",
45
- "tmp/main/.gitignore"
44
+ "tmp/.gitignore"
46
45
  ]
47
46
  s.homepage = %q{http://github.com/viking/butternut}
48
47
  s.rdoc_options = ["--charset=UTF-8"]
@@ -1,7 +1,5 @@
1
- require 'cucumber/formatter/ordered_xml_markup'
2
- require 'cucumber/formatter/duration'
1
+ require 'cucumber/formatter/html'
3
2
 
4
- require 'tmpdir'
5
3
  require 'fileutils'
6
4
  require 'nokogiri'
7
5
  require 'uri'
@@ -9,467 +7,136 @@ require 'open-uri'
9
7
  require 'net/ftp' # For Net::FTPPermError
10
8
 
11
9
  module Butternut
12
- class Formatter
13
- include ERB::Util # for the #h method
14
- include Cucumber::Formatter::Duration
15
-
16
- # FIXME: this is obviously not nominal, but I have no way of
17
- # accepting additional options from Cucumber at present
18
- FEATURES_DIR_PARTS = %w{.. features}
19
- FEATURES_HTML_PREFIX = "/features"
10
+ class Formatter < Cucumber::Formatter::Html
20
11
 
21
12
  def initialize(step_mother, io, options)
22
- @io = io
23
- @options = options
24
- @buffer = {}
25
- @current_builder = create_builder(@io)
26
-
27
- if @options && @options[:formats]
28
- format = @options[:formats].detect { |(name, _)| underscore(name) == "butternut/formatter" }
29
- if format && format[1].is_a?(String)
30
- base_dir = File.dirname(File.expand_path(format[1]))
31
- features_dir = File.expand_path(File.join(base_dir, *FEATURES_DIR_PARTS))
32
-
33
- if File.exist?(features_dir)
34
- today = Date.today.to_s
35
-
36
- @source_output_dir = File.join(features_dir, today)
37
- @source_html_path = FEATURES_HTML_PREFIX + "/" + today
38
- if !File.exist?(@source_output_dir)
39
- FileUtils.mkdir(@source_output_dir)
40
- end
41
- else
42
- $stderr.puts "Directory not found: #{features_dir}"
43
- end
44
- end
45
- end
46
- @source_output_dir ||= Dir.tmpdir
47
- @source_html_path ||= "file://" + Dir.tmpdir
48
- end
49
-
50
- def before_features(features)
51
- start_buffering :features
52
- end
53
-
54
- def after_features(features)
55
- stop_buffering :features
56
- # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
57
- builder.declare!(
58
- :DOCTYPE,
59
- :html,
60
- :PUBLIC,
61
- '-//W3C//DTD XHTML 1.0 Strict//EN',
62
- 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
63
- )
64
- builder.html(:xmlns => 'http://www.w3.org/1999/xhtml') do
65
- builder.head do
66
- builder.meta(:content => 'text/html;charset=utf-8')
67
- builder.title 'Cucumber'
68
- inline_css
69
- end
70
- builder.body do
71
- builder.div(:class => 'cucumber') do
72
- builder << buffer(:features)
73
- builder.div(format_duration(features.duration), :class => 'duration')
74
- end
75
- end
13
+ # find the format options
14
+ format = options[:formats].detect { |(name, _)| name == "Butternut::Formatter" }
15
+ if !format || !format[1].is_a?(String)
16
+ raise "Butternut::Formatter cannot output to STDOUT"
76
17
  end
77
- end
78
-
79
- def before_feature(feature)
80
- start_buffering :feature
81
- @exceptions = []
82
- end
18
+ out = format[1]
83
19
 
84
- def after_feature(feature)
85
- stop_buffering :feature
86
- builder.div(:class => 'feature') do
87
- builder << buffer(:feature)
88
- end
89
- end
90
-
91
- def before_comment(comment)
92
- start_buffering :comment
93
- end
94
-
95
- def after_comment(comment)
96
- stop_buffering :comment
97
- builder.pre(:class => 'comment') do
98
- builder << buffer(:comment)
99
- end
100
- end
101
-
102
- def comment_line(comment_line)
103
- builder.text!(comment_line)
104
- builder.br
105
- end
106
-
107
- def after_tags(tags)
108
- @tag_spacer = nil
109
- end
110
-
111
- def tag_name(tag_name)
112
- builder.text!(@tag_spacer) if @tag_spacer
113
- @tag_spacer = ' '
114
- builder.span(tag_name, :class => 'tag')
115
- end
116
-
117
- def feature_name(name)
118
- lines = name.split(/\r?\n/)
119
- return if lines.empty?
120
- builder.h2 do |h2|
121
- builder.span(lines[0], :class => 'val')
122
- end
123
- builder.p(:class => 'narrative') do
124
- lines[1..-1].each do |line|
125
- builder.text!(line.strip)
126
- builder.br
20
+ super
21
+ if File.directory?(out)
22
+ #@assets_dir = out
23
+ #@assets_url = "."
24
+ else
25
+ basename = File.basename(out).sub(/\..*$/, "")
26
+ @assets_dir = File.join(File.dirname(out), basename)
27
+ @assets_url = basename
28
+ if !File.exist?(@assets_dir)
29
+ FileUtils.mkdir(@assets_dir)
127
30
  end
128
31
  end
129
32
  end
130
33
 
131
- def before_background(background)
132
- @in_background = true
133
- start_buffering :background
134
- end
135
-
136
- def after_background(background)
137
- stop_buffering :background
138
- @in_background = nil
139
- builder.div(:class => 'background') do
140
- builder << buffer(:background)
141
- end
142
- end
143
-
144
- def background_name(keyword, name, file_colon_line, source_indent)
145
- @listing_background = true
146
- builder.h3 do |h3|
147
- builder.span(keyword, :class => 'keyword')
148
- builder.text!(' ')
149
- builder.span(name, :class => 'val')
150
- end
151
- end
152
-
153
34
  def before_feature_element(feature_element)
154
- start_buffering :feature_element
35
+ super
155
36
  @feature_element = feature_element
156
37
  end
157
38
 
158
- def after_feature_element(feature_element)
159
- stop_buffering :feature_element
160
- css_class = {
161
- Cucumber::Ast::Scenario => 'scenario',
162
- Cucumber::Ast::ScenarioOutline => 'scenario outline'
163
- }[feature_element.class]
164
-
165
- builder.div(:class => css_class) do
166
- builder << buffer(:feature_element)
167
- end
168
- @open_step_list = true
169
- @feature_element = nil
170
- end
171
-
172
- def scenario_name(keyword, name, file_colon_line, source_indent)
173
- @listing_background = false
174
- builder.h3 do
175
- builder.span(keyword, :class => 'keyword')
176
- builder.text!(' ')
177
- builder.span(name, :class => 'val')
178
- end
179
- end
180
-
181
- def before_outline_table(outline_table)
182
- @outline_row = 0
183
- start_buffering :outline_table
184
- end
185
-
186
- def after_outline_table(outline_table)
187
- stop_buffering :outline_table
188
- builder.table do
189
- builder << buffer(:outline_table)
190
- end
191
- @outline_row = nil
192
- end
193
-
194
- def before_examples(examples)
195
- start_buffering :examples
196
- end
197
-
198
- def after_examples(examples)
199
- stop_buffering :examples
200
- builder.div(:class => 'examples') do
201
- builder << buffer(:examples)
202
- end
203
- end
204
-
205
- def examples_name(keyword, name)
206
- builder.h4 do
207
- builder.span(keyword, :class => 'keyword')
208
- builder.text!(' ')
209
- builder.span(name, :class => 'val')
210
- end
211
- end
212
-
213
- def before_steps(steps)
214
- start_buffering :steps
215
- end
216
-
217
- def after_steps(steps)
218
- stop_buffering :steps
219
- builder.ol do
220
- builder << buffer(:steps)
221
- end
222
- end
223
-
224
- def before_step(step)
225
- @step_id = step.dom_id
226
- end
227
-
228
- def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
229
- start_buffering :step_result
230
- @hide_this_step = false
231
- if exception
232
- if @exceptions.include?(exception)
233
- @hide_this_step = true
234
- return
235
- end
236
- @exceptions << exception
237
- end
238
- if status != :failed && @in_background ^ background
239
- @hide_this_step = true
240
- return
241
- end
242
- @status = status
243
- end
244
-
245
39
  def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
246
40
  stop_buffering :step_result
247
41
  return if @hide_this_step
248
42
  builder.li(:id => @step_id, :class => "step #{status}") do
249
- builder.div do
250
- builder << buffer(:step_result)
251
- end
252
- builder.div(:class => "page") do
253
- if @feature_element.respond_to?(:last_page_source)
254
- page_source = @feature_element.last_page_source
255
- if page_source
256
- page_url = @feature_element.last_page_url
257
- page_source = transform_page_source(page_source, page_url)
258
- path = source_file_name
259
- File.open(path, "w") { |f| f.print(page_source) }
260
-
261
- builder.a({:target => "_blank", :href => "#{@source_html_path}/#{File.basename(path)}"}) do
262
- builder.span(:class => "icon-show") { builder << "" }
263
- end
264
- end
265
- end
266
- end
43
+ add_page_source_link(builder)
44
+ builder << buffer(:step_result)
267
45
  end
268
46
  end
269
47
 
270
- def step_name(keyword, step_match, status, source_indent, background)
271
- @step_matches ||= []
272
- background_in_scenario = background && !@listing_background
273
- @skip_step = @step_matches.index(step_match) || background_in_scenario
274
- @step_matches << step_match
275
-
276
- unless @skip_step
277
- build_step(keyword, step_match, status)
278
- end
279
- end
280
-
281
- def exception(exception, status)
282
- return if @hide_this_step
283
- builder.pre(format_exception(exception), :class => status)
284
- end
285
-
286
- def before_multiline_arg(multiline_arg)
287
- start_buffering :multiline_arg
288
- end
289
-
290
- def after_multiline_arg(multiline_arg)
291
- stop_buffering :multiline_arg
292
- return if @hide_this_step || @skip_step
293
- if Cucumber::Ast::Table === multiline_arg
294
- builder.table do
295
- builder << buffer(:multiline_arg)
48
+ private
49
+ def add_page_source_link(builder)
50
+ if !@feature_element.respond_to?(:last_page_source) || @feature_element.last_page_source.nil?
51
+ # don't add a link of we haven't interacted with a webpage
52
+ return
296
53
  end
297
- else
298
- builder << buffer(:multiline_arg)
299
- end
300
- end
301
54
 
302
- def py_string(string)
303
- return if @hide_this_step
304
- builder.pre(:class => 'val') do |pre|
305
- builder << string.gsub("\n", '&#x000A;')
306
- end
307
- end
55
+ page_source = @feature_element.last_page_source
56
+ page_url = @feature_element.last_page_url
57
+ @feature_element.last_page_source = nil
58
+ @feature_element.last_page_url = nil
308
59
 
309
- def before_table_row(table_row)
310
- @row_id = table_row.dom_id
311
- @col_index = 0
312
- start_buffering :table_row
313
- end
60
+ page_source = transform_page_source(page_source, page_url)
61
+ path = source_file_name
62
+ File.open(path, "w") { |f| f.print(page_source) }
314
63
 
315
- def after_table_row(table_row)
316
- stop_buffering :table_row
317
- return if @hide_this_step
318
- builder.table(:id => @row_id) do
319
- builder << buffer(:table_row)
320
- end
321
- if table_row.exception
322
- builder.tr do
323
- builder.td(:colspan => @col_index.to_s, :class => 'failed') do
324
- builder.pre do |pre|
325
- pre << format_exception(table_row.exception)
326
- end
64
+ builder.div(:style => "float: right") do
65
+ builder.a({:target => "_blank", :href => "#{@assets_url}/#{File.basename(path)}"}) do
66
+ builder << "Source"
327
67
  end
328
68
  end
329
69
  end
330
- @outline_row += 1 if @outline_row
331
- end
332
-
333
- def table_cell_value(value, status)
334
- return if @hide_this_step
335
-
336
- cell_type = @outline_row == 0 ? :th : :td
337
- attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'val'}
338
- attributes[:class] += " #{status}" if status
339
- build_cell(cell_type, value, attributes)
340
- @col_index += 1
341
- end
342
70
 
343
- def announce(announcement)
344
- builder.pre(announcement, :class => 'announcement')
345
- end
346
-
347
- private
348
-
349
- def build_step(keyword, step_match, status)
350
- step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
351
- builder.div do |div|
352
- builder.span(keyword, :class => 'keyword')
353
- builder.text!(' ')
354
- builder.span(:class => 'step val') do |name|
355
- name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
71
+ def source_file_name
72
+ t = Time.now.strftime("%Y%m%d")
73
+ path = nil
74
+ while path.nil?
75
+ path = File.join(@assets_dir, "butternut#{t}-#{$$}-#{rand(0x100000000).to_s(36)}.html")
76
+ path = nil if File.exist?(path)
356
77
  end
78
+ path
357
79
  end
358
- end
359
80
 
360
- def build_cell(cell_type, value, attributes)
361
- builder.__send__(cell_type, value, attributes)
362
- end
81
+ def transform_page_source(page_source, page_url)
82
+ base_uri = URI.parse(page_url)
83
+ base_uri.query = nil
84
+ @already_collected = []
363
85
 
364
- def inline_css
365
- builder.style(:type => 'text/css') do
366
- builder.text!(File.read(File.dirname(__FILE__) + '/cucumber.css'))
367
- end
368
- end
369
-
370
- def format_exception(exception)
371
- (["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n")
372
- end
86
+ doc = Nokogiri.HTML(page_source)
87
+ { :image => ['img', 'src'],
88
+ :stylesheet => ['link[rel=stylesheet]', 'href']
89
+ }.each_pair do |type, (selector, attr)|
90
+ doc.css(selector).each do |elt|
91
+ elt_url = elt[attr]
92
+ next if elt_url.nil? || elt_url.empty?
373
93
 
374
- def builder
375
- @current_builder
376
- end
377
-
378
- def buffer(label)
379
- result = @buffer[label]
380
- @buffer[label] = ''
381
- result
382
- end
94
+ result = save_remote_file(base_uri, type, elt_url)
95
+ elt[attr] = result if result
96
+ end
97
+ end
383
98
 
384
- def start_buffering(label)
385
- @buffer[label] ||= ''
386
- @parent_builder ||= {}
387
- @parent_builder[label] = @current_builder
388
- @current_builder = create_builder(@buffer[label])
389
- end
99
+ # disable links
100
+ doc.css('a').each { |link| link['href'] = "#" }
390
101
 
391
- def stop_buffering(label)
392
- @current_builder = @parent_builder[label]
393
- end
102
+ # turn off scripts
103
+ doc.css('script').each { |s| s.unlink }
394
104
 
395
- def create_builder(io)
396
- Cucumber::Formatter::OrderedXmlMarkup.new(:target => io, :indent => 0)
397
- end
105
+ # disable form elements
106
+ doc.css('input, select, textarea').each { |x| x['disabled'] = 'disabled' }
398
107
 
399
- def source_file_name
400
- t = Time.now.strftime("%Y%m%d")
401
- path = nil
402
- while path.nil?
403
- path = File.join(@source_output_dir, "butternut#{t}-#{$$}-#{rand(0x100000000).to_s(36)}.html")
404
- path = nil if File.exist?(path)
108
+ doc.to_s
405
109
  end
406
- path
407
- end
408
-
409
- # Snagged from active_support
410
- def underscore(camel_cased_word)
411
- camel_cased_word.to_s.gsub(/::/, '/').
412
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
413
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
414
- tr("-", "_").
415
- downcase
416
- end
417
-
418
- def transform_page_source(page_source, page_url)
419
- base_uri = URI.parse(page_url)
420
- base_uri.query = nil
421
- @already_collected = []
422
-
423
- doc = Nokogiri.HTML(page_source)
424
- { :image => ['img', 'src'],
425
- :stylesheet => ['link[rel=stylesheet]', 'href']
426
- }.each_pair do |type, (selector, attr)|
427
- doc.css(selector).each do |elt|
428
- elt_url = elt[attr]
429
- next if elt_url.nil? || elt_url.empty?
430
110
 
431
- result = save_remote_file(base_uri, type, elt_url)
432
- elt[attr] = result if result
111
+ def transform_stylesheet(stylesheet_uri, content)
112
+ content.gsub(%r{url\(([^\)]+)\)}) do |_|
113
+ result = save_remote_file(stylesheet_uri, :image, $1)
114
+ "url(#{result || $1})"
433
115
  end
434
116
  end
435
117
 
436
- # disable links
437
- doc.css('a').each { |link| link['href'] = "#" }
438
-
439
- # turn off scripts
440
- doc.css('script').each { |s| s.unlink }
441
-
442
- # disable form elements
443
- doc.css('input, select, textarea').each { |x| x['disabled'] = 'disabled' }
444
-
445
- doc.to_s
446
- end
447
-
448
- def transform_stylesheet(stylesheet_uri, content)
449
- content.gsub(%r{url\(([^\)]+)\)}) do |_|
450
- result = save_remote_file(stylesheet_uri, :image, $1)
451
- "url(#{result || $1})"
452
- end
453
- end
454
-
455
- def save_remote_file(base_uri, type, url)
456
- # FIXME: two different files could have the same basename :)
457
- uri = URI.parse(url)
458
- remote_uri = uri.absolute? ? uri : base_uri.merge(uri)
459
- basename = File.basename(uri.path)
460
-
461
- unless @already_collected.include?(remote_uri)
118
+ def save_remote_file(base_uri, type, url)
119
+ # FIXME: two different files could have the same basename :)
462
120
  begin
463
- content = open(remote_uri.to_s).read
464
- content = transform_stylesheet(remote_uri, content) if type == :stylesheet
465
- local_path = File.join(@source_output_dir, basename)
466
- File.open(local_path, "w") { |f| f.write(content) }
467
- @already_collected << remote_uri
468
- rescue Errno::ENOENT, OpenURI::HTTPError, Net::FTPPermError
121
+ uri = URI.parse(url)
122
+ rescue URI::InvalidURIError
469
123
  return nil
470
124
  end
125
+ remote_uri = uri.absolute? ? uri : base_uri.merge(uri)
126
+ basename = File.basename(uri.path)
127
+
128
+ unless @already_collected.include?(remote_uri)
129
+ begin
130
+ content = open(remote_uri.to_s).read
131
+ content = transform_stylesheet(remote_uri, content) if type == :stylesheet
132
+ local_path = File.join(@assets_dir, basename)
133
+ File.open(local_path, "w") { |f| f.write(content) }
134
+ @already_collected << remote_uri
135
+ rescue IOError, Errno::ENOENT, OpenURI::HTTPError, Net::FTPPermError
136
+ return nil
137
+ end
138
+ end
139
+ basename
471
140
  end
472
- basename
473
- end
474
141
  end
475
142
  end
data/lib/butternut.rb CHANGED
@@ -6,20 +6,15 @@ module Butternut
6
6
  def self.setup_hooks(obj)
7
7
  obj.instance_exec do
8
8
  AfterStep do |object|
9
- begin
10
- if object.is_a?(Cucumber::Ast::Scenario)
11
- if page_changed?
12
- object.last_page_source = current_page_source
13
- object.last_page_url = current_url
14
- else
15
- object.last_page_source = nil
16
- object.last_page_url = nil
17
- end
18
- @page_changed = false
9
+ if object.is_a?(Cucumber::Ast::Scenario)
10
+ if page_changed?
11
+ object.last_page_source = current_page_source
12
+ object.last_page_url = current_url
13
+ else
14
+ object.last_page_source = nil
15
+ object.last_page_url = nil
19
16
  end
20
- rescue Exception => e
21
- p e
22
- pp caller
17
+ @page_changed = false
23
18
  end
24
19
  end
25
20
  end
@@ -5,19 +5,6 @@ module Butternut
5
5
  extend SpecHelperDsl
6
6
  include SpecHelper
7
7
 
8
- Spec::Matchers.define :have_css_node do |css, regexp|
9
- match do |doc|
10
- nodes = doc.css(css)
11
- nodes.detect{ |node| node.text =~ regexp }
12
- end
13
- end
14
-
15
- Spec::Matchers.define :be_an_existing_file do
16
- match do |filename|
17
- File.exist?(filename)
18
- end
19
- end
20
-
21
8
  def setup_formatter(options = {})
22
9
  @out = StringIO.new
23
10
  @formatter = Butternut::Formatter.new(step_mother, @out, options)
@@ -33,191 +20,11 @@ module Butternut
33
20
  files.detect { |f| f.to_s =~ /\.html$/ }
34
21
  end
35
22
 
36
- describe "visiting blank feature name" do
37
- before(:each) do
38
- setup_formatter
39
- end
40
-
41
- it "should not raise an error when visiting a blank feature name" do
42
- lambda { @formatter.feature_name("") }.should_not raise_error
43
- end
44
- end
45
-
46
- describe "given a single feature" do
47
- before(:each) do
48
- setup_formatter
49
- run_defined_feature
50
- @doc = Nokogiri.HTML(@out.string)
51
- end
52
-
53
- describe "with a comment" do
54
- define_feature <<-FEATURE
55
- # Healthy
56
- FEATURE
57
-
58
- it { @out.string.should =~ /^\<!DOCTYPE/ }
59
- it { @out.string.should =~ /\<\/html\>$/ }
60
- it { @doc.should have_css_node('.feature .comment', /Healthy/) }
61
- end
62
-
63
- describe "with a tag" do
64
- define_feature <<-FEATURE
65
- @foo
66
- FEATURE
67
-
68
- it { @doc.should have_css_node('.feature .tag', /foo/) }
69
- end
70
-
71
- describe "with a narrative" do
72
- define_feature <<-FEATURE
73
- Feature: Bananas
74
- In order to find my inner monkey
75
- As a human
76
- I must eat bananas
77
- FEATURE
78
-
79
- it { @doc.should have_css_node('.feature h2', /Bananas/) }
80
- it { @doc.should have_css_node('.feature .narrative', /must eat bananas/) }
81
- end
82
-
83
- describe "with a background" do
84
- define_feature <<-FEATURE
85
- Feature: Bananas
86
-
87
- Background:
88
- Given there are bananas
89
- FEATURE
90
-
91
- it { @doc.should have_css_node('.feature .background', /there are bananas/) }
92
- end
93
-
94
- describe "with a scenario" do
95
- define_feature <<-FEATURE
96
- Scenario: Monkey eats banana
97
- Given there are bananas
98
- FEATURE
99
-
100
- it { @doc.should have_css_node('.feature h3', /Monkey eats banana/) }
101
- it { @doc.should have_css_node('.feature .scenario .step', /there are bananas/) }
102
- end
103
-
104
- describe "with a scenario outline" do
105
- define_feature <<-FEATURE
106
- Scenario Outline: Monkey eats a balanced diet
107
- Given there are <Things>
108
-
109
- Examples: Fruit
110
- | Things |
111
- | apples |
112
- | bananas |
113
- Examples: Vegetables
114
- | Things |
115
- | broccoli |
116
- | carrots |
117
- FEATURE
118
-
119
- it { @doc.should have_css_node('.feature .scenario.outline h4', /Fruit/) }
120
- it { @doc.should have_css_node('.feature .scenario.outline h4', /Vegetables/) }
121
- it { @doc.css('.feature .scenario.outline h4').length.should == 2}
122
- it { @doc.should have_css_node('.feature .scenario.outline table', //) }
123
- it { @doc.should have_css_node('.feature .scenario.outline table td', /carrots/) }
124
- end
125
-
126
- describe "with a step with a py string" do
127
- define_feature <<-FEATURE
128
- Scenario: Monkey goes to town
129
- Given there is a monkey called:
130
- """
131
- foo
132
- """
133
- FEATURE
134
-
135
- it { @doc.should have_css_node('.feature .scenario .val', /foo/) }
136
- end
137
-
138
- describe "with a multiline step arg" do
139
- define_feature <<-FEATURE
140
- Scenario: Monkey goes to town
141
- Given there are monkeys:
142
- | name |
143
- | foo |
144
- | bar |
145
- FEATURE
146
-
147
- it { @doc.should have_css_node('.feature .scenario table td', /foo/) }
148
- end
149
-
150
- describe "with a table in the background and the scenario" do
151
- define_feature <<-FEATURE
152
- Background:
153
- Given table:
154
- | a | b |
155
- | c | d |
156
- Scenario:
157
- Given another table:
158
- | e | f |
159
- | g | h |
160
- FEATURE
161
-
162
- it { @doc.css('td').length.should == 8 }
163
- end
164
-
165
- describe "with a py string in the background and the scenario" do
166
- define_feature <<-FEATURE
167
- Background:
168
- Given stuff:
169
- """
170
- foo
171
- """
172
- Scenario:
173
- Given more stuff:
174
- """
175
- bar
176
- """
177
- FEATURE
178
-
179
- it { @doc.css('.feature .background pre.val').length.should == 1 }
180
- it { @doc.css('.feature .scenario pre.val').length.should == 1 }
181
- end
182
-
183
- describe "with a step that fails in the scenario" do
184
- define_steps do
185
- Given(/boo/) { raise 'eek' }
186
- end
187
-
188
- define_feature(<<-FEATURE)
189
- Scenario: Monkey gets a fright
190
- Given boo
191
- FEATURE
192
-
193
- it { @doc.should have_css_node('.feature .scenario .step.failed', /eek/) }
194
- end
195
-
196
- describe "with a step that fails in the backgound" do
197
- define_steps do
198
- Given(/boo/) { raise 'eek' }
199
- end
200
-
201
- define_feature(<<-FEATURE)
202
- Background:
203
- Given boo
204
- Scenario:
205
- Given yay
206
- FEATURE
207
-
208
- it { @doc.should have_css_node('.feature .background .step.failed', /eek/) }
209
- it { @doc.should_not have_css_node('.feature .scenario .step.failed', //) }
210
- it { @doc.should have_css_node('.feature .scenario .step.undefined', /yay/) }
211
- end
23
+ it "should be a subclass of the html formatter" do
24
+ Butternut::Formatter.superclass.should == Cucumber::Formatter::Html
212
25
  end
213
26
 
214
- describe "displaying page source to stdout" do
215
- before(:each) do
216
- setup_formatter
217
- run_defined_feature
218
- @doc = Nokogiri.HTML(@out.string)
219
- end
220
-
27
+ describe "running without the --out option" do
221
28
  define_steps do
222
29
  Given(/foo/) do
223
30
  visit("file://" + File.expand_path(File.dirname(__FILE__) + "/../fixtures/foo.html"))
@@ -229,89 +36,110 @@ module Butternut
229
36
  Given foo
230
37
  FEATURE
231
38
 
232
- it do
233
- step = @doc.at('.feature .scenario .step.passed')
234
- link = step.at('a[href^="file:///tmp/"]')
235
- link.should_not be_nil
39
+ it "should raise an error" do
40
+ lambda {
41
+ setup_formatter
42
+ run_defined_feature
43
+ }.should raise_error
236
44
  end
237
45
  end
238
46
 
239
- describe "displaying page source to file" do
47
+ describe "running with the --out option" do
240
48
  before(:each) do
241
49
  dir = File.join(File.dirname(__FILE__), "..", "..", "tmp")
242
- setup_formatter({:formats => [
243
- ['Butternut::Formatter', File.join(dir, "main", "huge.html")]
244
- ]})
245
- run_defined_feature
246
- @doc = Nokogiri.HTML(@out.string)
50
+ @tmp_dir = File.join(dir, "#{Time.now.to_i}-#{rand(1000)}")
51
+ FileUtils.mkdir(@tmp_dir)
247
52
 
248
- @tmp_dir = File.join(dir, "features", Date.today.to_s)
249
- file = most_recent_html_file(@tmp_dir)
250
- @page_doc = Nokogiri.HTML(open(file).read)
53
+ #@tmp_dir = File.join(dir, "features", Date.today.to_s)
54
+ #file = most_recent_html_file(@tmp_dir)
55
+ #@page_doc = Nokogiri.HTML(open(file).read)
251
56
  end
252
57
 
253
- define_steps do
254
- Given(/foo/) do
255
- visit("file://" + File.expand_path(File.dirname(__FILE__) + "/../fixtures/foo.html"))
58
+ describe "with a filename specified" do
59
+ define_steps do
60
+ Given(/foo/) do
61
+ visit("file://" + File.join(FIXTURE_DIR, "foo.html"))
62
+ end
256
63
  end
257
- end
258
64
 
259
- define_feature(<<-FEATURE)
260
- Scenario: Monkey goes to the zoo
261
- Given foo
262
- FEATURE
65
+ define_feature(<<-FEATURE)
66
+ Scenario: Monkey goes to the zoo
67
+ Given foo
68
+ Then bar
69
+ FEATURE
263
70
 
264
- it "links to the page source" do
265
- step = @doc.at('.feature .scenario .step.passed')
266
- link = step.at("a")
267
- link.should_not be_nil
268
- file = link['href']
269
- file.should match(%r{^/features/#{Date.today.to_s}/butternut.+\.html})
270
- end
71
+ before(:each) do
72
+ setup_formatter({
73
+ :formats => [
74
+ ['Butternut::Formatter', File.join(@tmp_dir, "output.html")]
75
+ ]
76
+ })
77
+ run_defined_feature
78
+ @doc = Nokogiri.HTML(@out.string)
79
+
80
+ file = most_recent_html_file(File.join(@tmp_dir, "output"))
81
+ @page_doc = Nokogiri.HTML(open(file).read)
82
+ end
271
83
 
272
- it "saves images and stylesheets and rewrites urls in page source" do
273
- @page_doc.at('img:nth(1)')['src'].should == "picard.jpg"
274
- File.join(@tmp_dir, "picard.jpg").should be_an_existing_file
84
+ it "creates assets directory" do
85
+ File.join(@tmp_dir, "output").should be_an_existing_directory
86
+ end
275
87
 
276
- @page_doc.at('link:nth(1)[rel="stylesheet"]')['href'].should == "foo.css"
277
- File.join(@tmp_dir, "foo.css").should be_an_existing_file
88
+ it "links to the page source" do
89
+ step = @doc.at('.feature .scenario .step.passed')
90
+ link = step.at("a")
91
+ link.should_not be_nil
92
+ file = link['href']
93
+ file.should match(%r{^output/butternut.+\.html})
94
+ end
278
95
 
279
- @page_doc.at('link:nth(2)[rel="stylesheet"]')['href'].should == "bar.css"
280
- File.join(@tmp_dir, "bar.css").should be_an_existing_file
281
- end
96
+ it "saves images and stylesheets and rewrites urls in page source" do
97
+ @page_doc.at('img:nth(1)')['src'].should == "picard.jpg"
98
+ File.join(@tmp_dir, "output", "picard.jpg").should be_an_existing_file
282
99
 
283
- it "saves assets and rewrites urls referred to by stylesheets" do
284
- foo = open(File.join(@tmp_dir, "foo.css")).read
285
- foo.should include("url(facepalm.jpg)")
286
- File.join(@tmp_dir, "facepalm.jpg").should be_an_existing_file
287
- end
100
+ @page_doc.at('link:nth(1)[rel="stylesheet"]')['href'].should == "foo.css"
101
+ File.join(@tmp_dir, "output", "foo.css").should be_an_existing_file
288
102
 
289
- it "turns off links" do
290
- @page_doc.css('a').each do |link|
291
- link['href'].should == "#"
103
+ @page_doc.at('link:nth(2)[rel="stylesheet"]')['href'].should == "bar.css"
104
+ File.join(@tmp_dir, "output", "bar.css").should be_an_existing_file
292
105
  end
293
- end
294
106
 
295
- it "turns off scripts" do
296
- @page_doc.css('script').length.should == 0
297
- end
107
+ it "saves assets and rewrites urls referred to by stylesheets" do
108
+ foo = open(File.join(@tmp_dir, "output", "foo.css")).read
109
+ foo.should include("url(facepalm.jpg)")
110
+ File.join(@tmp_dir, "output", "facepalm.jpg").should be_an_existing_file
111
+ end
298
112
 
299
- it "disables form elements" do
300
- @page_doc.css('input, select, textarea').each do |elt|
301
- elt['disabled'].should == "disabled"
113
+ it "turns off links" do
114
+ @page_doc.css('a').each do |link|
115
+ link['href'].should == "#"
116
+ end
302
117
  end
303
- end
304
118
 
305
- it "handles Errno::ENOENT" do
306
- @page_doc.at('img:nth(2)')['src'].should == "/roflpwnage/missing_file_omg.gif"
307
- end
119
+ it "turns off scripts" do
120
+ @page_doc.css('script').length.should == 0
121
+ end
308
122
 
309
- it "handles OpenURI::HTTPError" do
310
- @page_doc.at('img:nth(3)')['src'].should == "http://google.com/missing_file_omg.gif"
311
- end
123
+ it "disables form elements" do
124
+ @page_doc.css('input, select, textarea').each do |elt|
125
+ elt['disabled'].should == "disabled"
126
+ end
127
+ end
312
128
 
313
- it "handles Net::FTPPermError" do
314
- @page_doc.at('img:nth(4)')['src'].should == "ftp://mirror.anl.gov/missing_file_omg.gif"
129
+ it "handles Errno::ENOENT" do
130
+ @page_doc.at('img:nth(2)')['src'].should == "/roflpwnage/missing_file_omg.gif"
131
+ end
132
+
133
+ it "handles OpenURI::HTTPError" do
134
+ @page_doc.at('img:nth(3)')['src'].should == "http://google.com/missing_file_omg.gif"
135
+ end
136
+
137
+ it "handles Net::FTPPermError" do
138
+ @page_doc.at('img:nth(4)')['src'].should == "ftp://mirror.anl.gov/missing_file_omg.gif"
139
+ end
140
+
141
+ it "handles badly formed URI's" do
142
+ end
315
143
  end
316
144
  end
317
145
  end
@@ -20,6 +20,7 @@
20
20
  <img src="/roflpwnage/missing_file_omg.gif"/>
21
21
  <img src="http://google.com/missing_file_omg.gif"/>
22
22
  <img src="ftp://mirror.anl.gov/missing_file_omg.gif"/>
23
+ <img src="//baduri/pants.png"/>
23
24
 
24
25
  <div class="facepalm">&nbsp;</div>
25
26
  </body>
data/spec/spec.opts CHANGED
@@ -1,2 +1 @@
1
1
  --color
2
- --backtrace
data/spec/spec_helper.rb CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  gem 'rspec'
3
3
  require 'spec'
4
4
  require 'spec/autorun'
5
- require 'ruby-debug'
5
+ require 'fileutils'
6
6
 
7
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
8
8
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
@@ -71,6 +71,25 @@ module SpecHelper
71
71
  end
72
72
  end
73
73
 
74
+ FIXTURE_DIR = File.expand_path(File.join(File.dirname(__FILE__), "fixtures"))
75
+
76
+ Spec::Matchers.define :be_an_existing_file do
77
+ match { |filename| File.exist?(filename) }
78
+ end
79
+
80
+ Spec::Matchers.define :be_an_existing_directory do
81
+ match { |filename| File.directory?(filename) }
82
+ end
83
+
84
+ Spec::Matchers.define :match_content_of do |expected|
85
+ match do |actual|
86
+ raise "expected file doesn't exist" unless File.exist?(expected)
87
+ raise "actual file doesn't exist" unless File.exist?(actual)
88
+ open(expected).read == open(actual).read
89
+ end
90
+ end
91
+
92
+
74
93
  Spec::Runner.configure do |config|
75
94
  config.before(:each) do
76
95
  Cucumber::Parser::NaturalLanguage.instance_variable_set(:@languages, nil)
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: butternut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Stephens
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-10 00:00:00 Z
12
+ date: 2009-12-14 00:00:00 Z
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -86,8 +86,7 @@ files:
86
86
  - spec/fixtures/picard.jpg
87
87
  - spec/spec.opts
88
88
  - spec/spec_helper.rb
89
- - tmp/features/.gitignore
90
- - tmp/main/.gitignore
89
+ - tmp/.gitignore
91
90
  has_rdoc: true
92
91
  homepage: http://github.com/viking/butternut
93
92
  licenses: []
data/tmp/main/.gitignore DELETED
@@ -1,2 +0,0 @@
1
- *
2
- !.gitignore