dchelimsky-rspec-stories 1.0.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.
Files changed (79) hide show
  1. data/History.txt +5 -0
  2. data/License.txt +22 -0
  3. data/Manifest.txt +78 -0
  4. data/README.txt +23 -0
  5. data/Rakefile +87 -0
  6. data/init.rb +4 -0
  7. data/lib/spec/runner/formatter/story/html_formatter.rb +174 -0
  8. data/lib/spec/runner/formatter/story/plain_text_formatter.rb +194 -0
  9. data/lib/spec/runner/formatter/story/progress_bar_formatter.rb +42 -0
  10. data/lib/spec/runner/options_extensions.rb +25 -0
  11. data/lib/spec/stories.rb +11 -0
  12. data/lib/spec/story/extensions.rb +3 -0
  13. data/lib/spec/story/extensions/main.rb +86 -0
  14. data/lib/spec/story/extensions/regexp.rb +9 -0
  15. data/lib/spec/story/extensions/string.rb +9 -0
  16. data/lib/spec/story/given_scenario.rb +14 -0
  17. data/lib/spec/story/runner.rb +57 -0
  18. data/lib/spec/story/runner/plain_text_story_runner.rb +48 -0
  19. data/lib/spec/story/runner/scenario_collector.rb +18 -0
  20. data/lib/spec/story/runner/scenario_runner.rb +54 -0
  21. data/lib/spec/story/runner/story_mediator.rb +137 -0
  22. data/lib/spec/story/runner/story_parser.rb +247 -0
  23. data/lib/spec/story/runner/story_runner.rb +74 -0
  24. data/lib/spec/story/scenario.rb +14 -0
  25. data/lib/spec/story/step.rb +70 -0
  26. data/lib/spec/story/step_group.rb +89 -0
  27. data/lib/spec/story/step_mother.rb +38 -0
  28. data/lib/spec/story/story.rb +39 -0
  29. data/lib/spec/story/version.rb +15 -0
  30. data/lib/spec/story/world.rb +124 -0
  31. data/resources/rake/verify_rcov.rake +7 -0
  32. data/rspec-stories.gemspec +35 -0
  33. data/spec/spec.opts +6 -0
  34. data/spec/spec/runner/formatter/story/html_formatter_spec.rb +135 -0
  35. data/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb +600 -0
  36. data/spec/spec/runner/formatter/story/progress_bar_formatter_spec.rb +82 -0
  37. data/spec/spec/runner/most_recent_spec.rb +0 -0
  38. data/spec/spec/runner/options_extensions_spec.rb +31 -0
  39. data/spec/spec/runner/resources/a_bar.rb +0 -0
  40. data/spec/spec/runner/resources/a_foo.rb +0 -0
  41. data/spec/spec/runner/resources/a_spec.rb +1 -0
  42. data/spec/spec/runner/resources/custom_example_group_runner.rb +14 -0
  43. data/spec/spec/runner/resources/utf8_encoded.rb +7 -0
  44. data/spec/spec/runner_spec.rb +11 -0
  45. data/spec/spec/spec_classes.rb +133 -0
  46. data/spec/spec/story/builders.rb +46 -0
  47. data/spec/spec/story/extensions/main_spec.rb +161 -0
  48. data/spec/spec/story/extensions_spec.rb +14 -0
  49. data/spec/spec/story/given_scenario_spec.rb +27 -0
  50. data/spec/spec/story/runner/plain_text_story_runner_spec.rb +90 -0
  51. data/spec/spec/story/runner/scenario_collector_spec.rb +27 -0
  52. data/spec/spec/story/runner/scenario_runner_spec.rb +214 -0
  53. data/spec/spec/story/runner/story_mediator_spec.rb +143 -0
  54. data/spec/spec/story/runner/story_parser_spec.rb +401 -0
  55. data/spec/spec/story/runner/story_runner_spec.rb +294 -0
  56. data/spec/spec/story/runner_spec.rb +93 -0
  57. data/spec/spec/story/scenario_spec.rb +18 -0
  58. data/spec/spec/story/step_group_spec.rb +157 -0
  59. data/spec/spec/story/step_mother_spec.rb +84 -0
  60. data/spec/spec/story/step_spec.rb +272 -0
  61. data/spec/spec/story/story_helper.rb +2 -0
  62. data/spec/spec/story/story_spec.rb +84 -0
  63. data/spec/spec/story/world_spec.rb +423 -0
  64. data/spec/spec_helper.rb +84 -0
  65. data/story_server/prototype/javascripts/builder.js +136 -0
  66. data/story_server/prototype/javascripts/controls.js +972 -0
  67. data/story_server/prototype/javascripts/dragdrop.js +976 -0
  68. data/story_server/prototype/javascripts/effects.js +1117 -0
  69. data/story_server/prototype/javascripts/prototype.js +4140 -0
  70. data/story_server/prototype/javascripts/rspec.js +149 -0
  71. data/story_server/prototype/javascripts/scriptaculous.js +58 -0
  72. data/story_server/prototype/javascripts/slider.js +276 -0
  73. data/story_server/prototype/javascripts/sound.js +55 -0
  74. data/story_server/prototype/javascripts/unittest.js +568 -0
  75. data/story_server/prototype/lib/server.rb +24 -0
  76. data/story_server/prototype/stories.html +176 -0
  77. data/story_server/prototype/stylesheets/rspec.css +136 -0
  78. data/story_server/prototype/stylesheets/test.css +90 -0
  79. metadata +154 -0
@@ -0,0 +1,42 @@
1
+ require 'spec/runner/formatter/story/plain_text_formatter'
2
+
3
+ module Spec
4
+ module Runner
5
+ module Formatter
6
+ module Story
7
+ class ProgressBarFormatter < PlainTextFormatter
8
+
9
+ def story_started(title, narrative) end
10
+ def story_ended(title, narrative) end
11
+
12
+ def run_started(count)
13
+ @start_time = Time.now
14
+ super
15
+ end
16
+
17
+ def run_ended
18
+ @output.puts
19
+ @output.puts
20
+ @output.puts "Finished in %f seconds" % (Time.now - @start_time)
21
+ @output.puts
22
+ super
23
+ end
24
+
25
+ def scenario_ended
26
+ if @scenario_failed
27
+ @output.print red('F')
28
+ @output.flush
29
+ elsif @scenario_pending
30
+ @output.print yellow('P')
31
+ @output.flush
32
+ else
33
+ @output.print green('.')
34
+ @output.flush
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec/runner/options'
2
+
3
+ module Spec
4
+ module Runner
5
+ module OptionsExtensions
6
+
7
+ STORY_FORMATTERS = {
8
+ 'plain' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'],
9
+ 'p' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'],
10
+ 'html' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter'],
11
+ 'h' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter'],
12
+ 'progress' => ['spec/runner/formatter/story/progress_bar_formatter', 'Formatter::Story::ProgressBarFormatter'],
13
+ 'r' => ['spec/runner/formatter/story/progress_bar_formatter', 'Formatter::Story::ProgressBarFormatter']
14
+ }
15
+
16
+ def story_formatters
17
+ @format_options ||= [['plain', @output_stream]]
18
+ @formatters ||= load_formatters(@format_options, STORY_FORMATTERS)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
25
+ Spec::Runner::Options.send(:include, Spec::Runner::OptionsExtensions)
@@ -0,0 +1,11 @@
1
+ require 'spec/expectations'
2
+ require 'spec/runner/options_extensions'
3
+ require 'spec/story/extensions'
4
+ require 'spec/story/given_scenario'
5
+ require 'spec/story/runner'
6
+ require 'spec/story/scenario'
7
+ require 'spec/story/step'
8
+ require 'spec/story/step_group'
9
+ require 'spec/story/step_mother'
10
+ require 'spec/story/story'
11
+ require 'spec/story/world'
@@ -0,0 +1,3 @@
1
+ require 'spec/story/extensions/main'
2
+ require 'spec/story/extensions/string'
3
+ require 'spec/story/extensions/regexp'
@@ -0,0 +1,86 @@
1
+ module Spec
2
+ module Story
3
+ module Extensions
4
+ module Main
5
+ def Story(title, narrative, params = {}, &body)
6
+ ::Spec::Story::Runner.story_runner.Story(title, narrative, params, &body)
7
+ end
8
+
9
+ # Calling this deprecated is silly, since it hasn't been released yet. But, for
10
+ # those who are reading this - this will be deleted before the 1.1 release.
11
+ def run_story(*args, &block)
12
+ runner = Spec::Story::Runner::PlainTextStoryRunner.new(*args)
13
+ runner.instance_eval(&block) if block
14
+ runner.run
15
+ end
16
+
17
+ # Creates (or appends to an existing) a namespaced group of steps for use in Stories
18
+ #
19
+ # == Examples
20
+ #
21
+ # # Creating a new group
22
+ # steps_for :forms do
23
+ # When("user enters $value in the $field field") do ... end
24
+ # When("user submits the $form form") do ... end
25
+ # end
26
+ def steps_for(tag, &block)
27
+ steps = rspec_story_steps[tag]
28
+ steps.instance_eval(&block) if block
29
+ steps
30
+ end
31
+
32
+ # Creates a context for running a Plain Text Story with specific groups of Steps.
33
+ # Also supports adding arbitrary steps that will only be accessible to
34
+ # the Story being run.
35
+ #
36
+ # == Examples
37
+ #
38
+ # # Run a Story with one group of steps
39
+ # with_steps_for :checking_accounts do
40
+ # run File.dirname(__FILE__) + "/withdraw_cash"
41
+ # end
42
+ #
43
+ # # Run a Story, adding steps that are only available for this Story
44
+ # with_steps_for :accounts do
45
+ # Given "user is logged in as account administrator"
46
+ # run File.dirname(__FILE__) + "/reconcile_accounts"
47
+ # end
48
+ #
49
+ # # Run a Story with steps from two groups
50
+ # with_steps_for :checking_accounts, :savings_accounts do
51
+ # run File.dirname(__FILE__) + "/transfer_money"
52
+ # end
53
+ #
54
+ # # Run a Story with a specific Story extension
55
+ # with_steps_for :login, :navigation do
56
+ # run File.dirname(__FILE__) + "/user_changes_password", :type => RailsStory
57
+ # end
58
+ def with_steps_for(*tags, &block)
59
+ steps = Spec::Story::StepGroup.new do
60
+ extend StoryRunnerStepGroupAdapter
61
+ end
62
+ tags.each {|tag| steps << rspec_story_steps[tag]}
63
+ steps.instance_eval(&block) if block
64
+ steps
65
+ end
66
+
67
+ private
68
+
69
+ module StoryRunnerStepGroupAdapter
70
+ def run(path, options={})
71
+ runner = Spec::Story::Runner::PlainTextStoryRunner.new(path, options)
72
+ runner.steps << self
73
+ runner.run
74
+ end
75
+ end
76
+
77
+ def rspec_story_steps # :nodoc:
78
+ $rspec_story_steps ||= Spec::Story::StepGroupHash.new
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ include Spec::Story::Extensions::Main
@@ -0,0 +1,9 @@
1
+ class Regexp
2
+ def step_name
3
+ self.source.gsub '\\$', '$$'
4
+ end
5
+
6
+ def arg_regexp
7
+ ::Spec::Story::Step::PARAM_OR_GROUP_PATTERN
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+ def step_name
3
+ self
4
+ end
5
+
6
+ def arg_regexp
7
+ ::Spec::Story::Step::PARAM_OR_GROUP_PATTERN
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ module Spec
2
+ module Story
3
+ class GivenScenario
4
+ def initialize(name)
5
+ @name = name
6
+ end
7
+
8
+ def perform(instance, ignore_name)
9
+ scenario = Runner::StoryRunner.scenario_from_current_story(@name)
10
+ Runner::ScenarioRunner.new.run(scenario, instance)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec/story/runner/scenario_collector.rb'
2
+ require 'spec/story/runner/scenario_runner.rb'
3
+ require 'spec/story/runner/story_runner.rb'
4
+ require 'spec/story/runner/story_parser.rb'
5
+ require 'spec/story/runner/story_mediator.rb'
6
+ require 'spec/story/runner/plain_text_story_runner.rb'
7
+
8
+ module Spec
9
+ module Story
10
+ module Runner
11
+ def self.run_options # :nodoc:
12
+ Spec::Runner.options
13
+ end
14
+
15
+ def self.story_runner # :nodoc:
16
+ unless @story_runner
17
+ @story_runner = create_story_runner
18
+ run_options.story_formatters.each do |formatter|
19
+ register_listener(formatter)
20
+ end
21
+ self.register_exit_hook
22
+ end
23
+ @story_runner
24
+ end
25
+
26
+ def self.scenario_runner # :nodoc:
27
+ @scenario_runner ||= ScenarioRunner.new
28
+ end
29
+
30
+ def self.world_creator # :nodoc:
31
+ @world_creator ||= World
32
+ end
33
+
34
+ def self.create_story_runner
35
+ Runner::StoryRunner.new(scenario_runner, world_creator)
36
+ end
37
+
38
+ # Use this to register a customer output formatter.
39
+ def self.register_listener(listener)
40
+ story_runner.add_listener(listener) # run_started, story_started, story_ended, #run_ended
41
+ world_creator.add_listener(listener) # found_scenario, step_succeeded, step_failed, step_failed
42
+ scenario_runner.add_listener(listener) # scenario_started, scenario_succeeded, scenario_pending, scenario_failed
43
+ end
44
+
45
+ def self.register_exit_hook # :nodoc:
46
+ at_exit do
47
+ exit Runner.story_runner.run_stories unless $!
48
+ end
49
+ end
50
+
51
+ def self.dry_run
52
+ run_options.dry_run
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,48 @@
1
+ module Spec
2
+ module Story
3
+ module Runner
4
+ class PlainTextStoryRunner
5
+ # You can initialize a PlainTextStoryRunner with the path to the
6
+ # story file or a block, in which you can define the path using load.
7
+ #
8
+ # == Examples
9
+ #
10
+ # PlainTextStoryRunner.new('path/to/file')
11
+ #
12
+ # PlainTextStoryRunner.new do |runner|
13
+ # runner.load 'path/to/file'
14
+ # end
15
+ def initialize(*args)
16
+ @options = Hash === args.last ? args.pop : {}
17
+ @story_file = args.empty? ? nil : args.shift
18
+ yield self if block_given?
19
+ end
20
+
21
+ def []=(key, value)
22
+ @options[key] = value
23
+ end
24
+
25
+ def load(path)
26
+ @story_file = path
27
+ end
28
+
29
+ def run(story_runner=Spec::Story::Runner.story_runner)
30
+ raise "You must set a path to the file with the story. See the RDoc." if @story_file.nil?
31
+ mediator = Spec::Story::Runner::StoryMediator.new(steps, story_runner, @options)
32
+ parser = Spec::Story::Runner::StoryParser.new(mediator)
33
+
34
+ story_text = File.read(@story_file)
35
+ parser.parse(story_text.split("\n"))
36
+
37
+ mediator.run_stories
38
+ end
39
+
40
+ def steps
41
+ @step_group ||= Spec::Story::StepGroup.new
42
+ yield @step_group if block_given?
43
+ @step_group
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ module Spec
2
+ module Story
3
+ module Runner
4
+ class ScenarioCollector
5
+ attr_accessor :scenarios
6
+
7
+ def initialize(story)
8
+ @story = story
9
+ @scenarios = []
10
+ end
11
+
12
+ def Scenario(name, &body)
13
+ @scenarios << Scenario.new(@story, name, &body)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ module Spec
2
+ module Story
3
+ module Runner
4
+ class ScenarioRunner
5
+ def initialize
6
+ @listeners = []
7
+ end
8
+
9
+ def run(scenario, world)
10
+ @listeners.each { |l| l.scenario_started(scenario.story.title, scenario.name) }
11
+ run_story_ignoring_scenarios(scenario.story, world)
12
+
13
+ world.start_collecting_errors
14
+
15
+ unless scenario.body
16
+ @listeners.each { |l| l.scenario_pending(scenario.story.title, scenario.name, '') }
17
+ return true
18
+ end
19
+
20
+ world.instance_eval(&scenario.body)
21
+ if world.errors.empty?
22
+ @listeners.each { |l| l.scenario_succeeded(scenario.story.title, scenario.name) }
23
+ else
24
+ if Spec::Example::ExamplePendingError === (e = world.errors.first)
25
+ @listeners.each { |l| l.scenario_pending(scenario.story.title, scenario.name, e.message) }
26
+ else
27
+ @listeners.each { |l| l.scenario_failed(scenario.story.title, scenario.name, e) }
28
+ return false
29
+ end
30
+ end
31
+ true
32
+ end
33
+
34
+ def add_listener(listener)
35
+ @listeners << listener
36
+ end
37
+
38
+ private
39
+
40
+ def run_story_ignoring_scenarios(story, world)
41
+ class << world
42
+ def Scenario(name, &block)
43
+ # do nothing
44
+ end
45
+ end
46
+ story.run_in(world)
47
+ class << world
48
+ remove_method(:Scenario)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,137 @@
1
+ module Spec
2
+ module Story
3
+ module Runner
4
+
5
+ class StoryMediator
6
+ def initialize(step_group, runner, options={})
7
+ @step_group = step_group
8
+ @stories = []
9
+ @runner = runner
10
+ @options = options
11
+ end
12
+
13
+ def stories
14
+ @stories.collect { |p| p.to_proc }
15
+ end
16
+
17
+ def create_story(title, narrative)
18
+ @stories << Story.new(title, narrative, @step_group, @options)
19
+ end
20
+
21
+ def create_scenario(title)
22
+ current_story.add_scenario Scenario.new(title)
23
+ end
24
+
25
+ def create_given(name)
26
+ current_scenario.add_step Step.new('Given', name)
27
+ end
28
+
29
+ def create_given_scenario(name)
30
+ current_scenario.add_step Step.new('GivenScenario', name)
31
+ end
32
+
33
+ def create_when(name)
34
+ current_scenario.add_step Step.new('When', name)
35
+ end
36
+
37
+ def create_then(name)
38
+ current_scenario.add_step Step.new('Then', name)
39
+ end
40
+
41
+ def last_step
42
+ current_scenario.last_step
43
+ end
44
+
45
+ def add_to_last(name)
46
+ last_step.name << name
47
+ end
48
+
49
+ def run_stories
50
+ stories.each { |story| @runner.instance_eval(&story) }
51
+ end
52
+
53
+ private
54
+ def current_story
55
+ @stories.last
56
+ end
57
+
58
+ def current_scenario
59
+ current_story.current_scenario
60
+ end
61
+
62
+ class Story
63
+ def initialize(title, narrative, step_group, options)
64
+ @title = title
65
+ @narrative = narrative
66
+ @scenarios = []
67
+ @step_group = step_group
68
+ @options = options
69
+ end
70
+
71
+ def to_proc
72
+ title = @title
73
+ narrative = @narrative
74
+ scenarios = @scenarios.collect { |scenario| scenario.to_proc }
75
+ options = @options.merge(:steps_for => @step_group)
76
+ lambda do
77
+ Story title, narrative, options do
78
+ scenarios.each { |scenario| instance_eval(&scenario) }
79
+ end
80
+ end
81
+ end
82
+
83
+ def add_scenario(scenario)
84
+ @scenarios << scenario
85
+ end
86
+
87
+ def current_scenario
88
+ @scenarios.last
89
+ end
90
+ end
91
+
92
+ class Scenario
93
+ def initialize(name)
94
+ @name = name
95
+ @steps = []
96
+ end
97
+
98
+ def to_proc
99
+ name = @name
100
+ steps = @steps.collect { |step| step.to_proc }
101
+ lambda do
102
+ Scenario name do
103
+ steps.each { |step| instance_eval(&step) }
104
+ end
105
+ end
106
+ end
107
+
108
+ def add_step(step)
109
+ @steps << step
110
+ end
111
+
112
+ def last_step
113
+ @steps.last
114
+ end
115
+ end
116
+
117
+ class Step
118
+ attr_reader :name
119
+
120
+ def initialize(type, name)
121
+ @type = type
122
+ @name = name
123
+ end
124
+
125
+ def to_proc
126
+ type = @type
127
+ name = @name
128
+ lambda do
129
+ send(type, name)
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ end
136
+ end
137
+ end