butternut 0.1.0 → 0.2.0

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.
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