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,247 @@
1
+ module Spec
2
+ module Story
3
+ module Runner
4
+
5
+ class IllegalStepError < StandardError
6
+ def initialize(state, event)
7
+ super("Illegal attempt to create a #{event} after a #{state}")
8
+ end
9
+ end
10
+
11
+ class StoryParser
12
+ def initialize(story_mediator)
13
+ @story_mediator = story_mediator
14
+ @current_story_lines = []
15
+ transition_to(:starting_state)
16
+ end
17
+
18
+ def parse(lines)
19
+ lines.reject! {|line| line == ""}
20
+ until lines.empty?
21
+ process_line(lines.shift)
22
+ end
23
+ @state.eof
24
+ end
25
+
26
+ def process_line(line)
27
+ line.strip!
28
+ case line
29
+ when /^#/ then @state.comment(line)
30
+ when /^Story: / then @state.story(line)
31
+ when /^Scenario: / then @state.scenario(line)
32
+ when /^Given:? / then @state.given(line)
33
+ when /^GivenScenario:? / then @state.given_scenario(line)
34
+ when /^When:? / then @state.event(line)
35
+ when /^Then:? / then @state.outcome(line)
36
+ when /^And:? / then @state.one_more_of_the_same(line)
37
+ else @state.other(line)
38
+ end
39
+ end
40
+
41
+ def init_story(title)
42
+ @current_story_lines.clear
43
+ add_story_line(title)
44
+ end
45
+
46
+ def add_story_line(line)
47
+ @current_story_lines << line
48
+ end
49
+
50
+ def create_story()
51
+ unless @current_story_lines.empty?
52
+ @story_mediator.create_story(@current_story_lines[0].gsub("Story: ",""), @current_story_lines[1..-1].join("\n"))
53
+ @current_story_lines.clear
54
+ end
55
+ end
56
+
57
+ def create_scenario(title)
58
+ @story_mediator.create_scenario(title.gsub("Scenario: ",""))
59
+ end
60
+
61
+ def create_given(name)
62
+ @story_mediator.create_given(name)
63
+ end
64
+
65
+ def create_given_scenario(name)
66
+ @story_mediator.create_given_scenario(name)
67
+ end
68
+
69
+ def create_when(name)
70
+ @story_mediator.create_when(name)
71
+ end
72
+
73
+ def create_then(name)
74
+ @story_mediator.create_then(name)
75
+ end
76
+
77
+ def add_to_last(line)
78
+ @story_mediator.add_to_last("\n#{line}")
79
+ end
80
+
81
+ def transition_to(key)
82
+ @state = states[key]
83
+ end
84
+
85
+ def states
86
+ @states ||= {
87
+ :starting_state => StartingState.new(self),
88
+ :story_state => StoryState.new(self),
89
+ :scenario_state => ScenarioState.new(self),
90
+ :given_state => GivenState.new(self),
91
+ :when_state => WhenState.new(self),
92
+ :then_state => ThenState.new(self)
93
+ }
94
+ end
95
+
96
+ class State
97
+ def initialize(parser)
98
+ @parser = parser
99
+ end
100
+
101
+ def story(line)
102
+ @parser.init_story(line)
103
+ @parser.transition_to(:story_state)
104
+ end
105
+
106
+ def scenario(line)
107
+ @parser.create_scenario(line)
108
+ @parser.transition_to(:scenario_state)
109
+ end
110
+
111
+ def given(line)
112
+ @parser.create_given(remove_tag_from(:given, line))
113
+ @parser.transition_to(:given_state)
114
+ end
115
+
116
+ def given_scenario(line)
117
+ @parser.create_given_scenario(remove_tag_from(:givenscenario, line))
118
+ @parser.transition_to(:given_state)
119
+ end
120
+
121
+ def event(line)
122
+ @parser.create_when(remove_tag_from(:when, line))
123
+ @parser.transition_to(:when_state)
124
+ end
125
+
126
+ def outcome(line)
127
+ @parser.create_then(remove_tag_from(:then, line))
128
+ @parser.transition_to(:then_state)
129
+ end
130
+
131
+ def remove_tag_from(tag, line)
132
+ tokens = line.split
133
+ # validation of tag can go here
134
+ tokens[0].downcase.match(/#{tag.to_s}:?/) ?
135
+ (tokens[1..-1].join(' ')) : line
136
+ end
137
+
138
+ def eof
139
+ end
140
+
141
+ def other(line)
142
+ # no-op - supports header text before the first story in a file
143
+ end
144
+
145
+ def comment(line)
146
+ end
147
+ end
148
+
149
+ class StartingState < State
150
+ def initialize(parser)
151
+ @parser = parser
152
+ end
153
+ end
154
+
155
+ class StoryState < State
156
+ def one_more_of_the_same(line)
157
+ other(line)
158
+ end
159
+
160
+ def story(line)
161
+ @parser.create_story
162
+ @parser.add_story_line(line)
163
+ end
164
+
165
+ def scenario(line)
166
+ @parser.create_story
167
+ @parser.create_scenario(line)
168
+ @parser.transition_to(:scenario_state)
169
+ end
170
+
171
+ def given(line)
172
+ other(line)
173
+ end
174
+
175
+ def event(line)
176
+ other(line)
177
+ end
178
+
179
+ def outcome(line)
180
+ other(line)
181
+ end
182
+
183
+ def other(line)
184
+ @parser.add_story_line(line)
185
+ end
186
+
187
+ def eof
188
+ @parser.create_story
189
+ end
190
+ end
191
+
192
+ class ScenarioState < State
193
+ def one_more_of_the_same(line)
194
+ raise IllegalStepError.new("Scenario", "And")
195
+ end
196
+
197
+ def scenario(line)
198
+ @parser.create_scenario(line)
199
+ end
200
+ end
201
+
202
+ class GivenState < State
203
+ def one_more_of_the_same(line)
204
+ @parser.create_given(remove_tag_from(:and, line))
205
+ end
206
+
207
+ def given(line)
208
+ @parser.create_given(remove_tag_from(:given, line))
209
+ end
210
+
211
+ def other(line)
212
+ @parser.add_to_last(line)
213
+ end
214
+ end
215
+
216
+ class WhenState < State
217
+ def one_more_of_the_same(line)
218
+ @parser.create_when(remove_tag_from(:and ,line))
219
+ end
220
+
221
+ def event(line)
222
+ @parser.create_when(remove_tag_from(:when ,line))
223
+ end
224
+
225
+ def other(line)
226
+ @parser.add_to_last(line)
227
+ end
228
+ end
229
+
230
+ class ThenState < State
231
+ def one_more_of_the_same(line)
232
+ @parser.create_then(remove_tag_from(:and ,line))
233
+ end
234
+
235
+ def outcome(line)
236
+ @parser.create_then(remove_tag_from(:then ,line))
237
+ end
238
+
239
+ def other(line)
240
+ @parser.add_to_last(line)
241
+ end
242
+ end
243
+
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,74 @@
1
+ module Spec
2
+ module Story
3
+ module Runner
4
+ class StoryRunner
5
+ def self.current_story_runner
6
+ @current_story_runner
7
+ end
8
+
9
+ def self.current_story_runner=(current_story_runner)
10
+ @current_story_runner = current_story_runner
11
+ end
12
+
13
+ def self.scenario_from_current_story(scenario_name)
14
+ current_story_runner.scenario_from_current_story(scenario_name)
15
+ end
16
+
17
+ attr_accessor :stories, :scenarios, :current_story
18
+
19
+ def initialize(scenario_runner, world_creator = World)
20
+ StoryRunner.current_story_runner = self
21
+ @scenario_runner = scenario_runner
22
+ @world_creator = world_creator
23
+ @stories = []
24
+ @scenarios_by_story = {}
25
+ @scenarios = []
26
+ @listeners = []
27
+ end
28
+
29
+ def Story(title, narrative, params = {}, &body)
30
+ story = Story.new(title, narrative, params, &body)
31
+ @stories << story
32
+
33
+ # collect scenarios
34
+ collector = ScenarioCollector.new(story)
35
+ story.run_in(collector)
36
+ @scenarios += collector.scenarios
37
+ @scenarios_by_story[story.title] = collector.scenarios
38
+ end
39
+
40
+ def run_stories
41
+ return if @stories.empty?
42
+ @listeners.each { |l| l.run_started(scenarios.size) }
43
+ success = true
44
+ @stories.each do |story|
45
+ story.assign_steps_to(World)
46
+ @current_story = story
47
+ @listeners.each { |l| l.story_started(story.title, story.narrative) }
48
+ scenarios = @scenarios_by_story[story.title]
49
+ scenarios.each do |scenario|
50
+ type = story[:type] || Object
51
+ args = story[:args] || []
52
+ world = @world_creator.create(type, *args)
53
+ success = success & @scenario_runner.run(scenario, world)
54
+ end
55
+ @listeners.each { |l| l.story_ended(story.title, story.narrative) }
56
+ World.step_mother.clear
57
+ end
58
+ unique_steps = (World.step_names.collect {|n| Regexp === n ? n.source : n.to_s}).uniq.sort
59
+ @listeners.each { |l| l.collected_steps(unique_steps) }
60
+ @listeners.each { |l| l.run_ended }
61
+ return success
62
+ end
63
+
64
+ def add_listener(listener)
65
+ @listeners << listener
66
+ end
67
+
68
+ def scenario_from_current_story(scenario_name)
69
+ @scenarios_by_story[@current_story.title].find {|s| s.name == scenario_name }
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,14 @@
1
+
2
+ module Spec
3
+ module Story
4
+ class Scenario
5
+ attr_accessor :name, :body, :story
6
+
7
+ def initialize(story, name, &body)
8
+ @story = story
9
+ @name = name
10
+ @body = body
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,70 @@
1
+ module Spec
2
+ module Story
3
+ class Step
4
+ PARAM_PATTERN = /([^\\]|^)(\$(?!\$)\w*)/
5
+ PARAM_OR_GROUP_PATTERN = /(\$(?!\$)\w*)|\(.*?\)/
6
+
7
+ attr_reader :name
8
+
9
+ def initialize(name, &block)
10
+ init_name(name)
11
+ init_expression(name)
12
+ block_given? ? init_module(name, &block) : set_pending
13
+ end
14
+
15
+ def perform(instance, *args)
16
+ raise Spec::Example::ExamplePendingError.new("Not Yet Implemented") if pending?
17
+ instance.extend(@mod)
18
+ instance.__send__(sanitize(@name), *args)
19
+ end
20
+
21
+ def matches?(name)
22
+ !(name.strip =~ @expression).nil?
23
+ end
24
+
25
+ def parse_args(name)
26
+ name.strip.match(@expression)[1..-1]
27
+ end
28
+
29
+ private
30
+
31
+ def sanitize(a_string_or_regexp)
32
+ return a_string_or_regexp.source if Regexp == a_string_or_regexp
33
+ a_string_or_regexp.to_s
34
+ end
35
+
36
+ def init_module(name, &block)
37
+ sanitized_name = sanitize(name)
38
+ @mod = Module.new do
39
+ define_method(sanitized_name, &block)
40
+ end
41
+ end
42
+
43
+ def set_pending
44
+ @pending = true
45
+ end
46
+
47
+ def pending?
48
+ @pending == true
49
+ end
50
+
51
+ def init_name(name)
52
+ @name = name
53
+ end
54
+
55
+ def init_expression(string_or_regexp)
56
+ if String === string_or_regexp
57
+ expression = string_or_regexp.dup
58
+ %w<? ( ) [ ] { } ^ !>.each {|c| expression.gsub! c, "\\#{c}"}
59
+ elsif Regexp === string_or_regexp
60
+ expression = string_or_regexp.source
61
+ end
62
+ while expression =~ PARAM_PATTERN
63
+ expression.sub!($2, "(.*?)")
64
+ end
65
+ @expression = Regexp.new("\\A#{expression}\\Z", Regexp::MULTILINE)
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,89 @@
1
+ module Spec
2
+ module Story
3
+
4
+ class StepGroupHash < Hash
5
+ def initialize
6
+ super do |h,k|
7
+ h[k] = Spec::Story::StepGroup.new
8
+ end
9
+ end
10
+ end
11
+
12
+ class StepGroup
13
+ def self.steps(&block)
14
+ @step_group ||= StepGroup.new(false)
15
+ @step_group.instance_eval(&block) if block
16
+ @step_group
17
+ end
18
+
19
+ def initialize(init_defaults=true, &block)
20
+ @hash_of_lists_of_steps = Hash.new {|h, k| h[k] = []}
21
+ if init_defaults
22
+ self.class.steps.add_to(self)
23
+ end
24
+ instance_eval(&block) if block
25
+ end
26
+
27
+ def find(type, name)
28
+ @hash_of_lists_of_steps[type].each do |step|
29
+ return step if step.matches?(name)
30
+ end
31
+ return nil
32
+ end
33
+
34
+ def GivenScenario(name, &block)
35
+ create_matcher(:given_scenario, name, &block)
36
+ end
37
+
38
+ def Given(name, &block)
39
+ create_matcher(:given, name, &block)
40
+ end
41
+
42
+ def When(name, &block)
43
+ create_matcher(:when, name, &block)
44
+ end
45
+
46
+ def Then(name, &block)
47
+ create_matcher(:then, name, &block)
48
+ end
49
+
50
+ alias :given_scenario :GivenScenario
51
+ alias :given :Given
52
+ alias :when :When
53
+ alias :then :Then
54
+
55
+ def add(type, steps)
56
+ (@hash_of_lists_of_steps[type] << steps).flatten!
57
+ end
58
+
59
+ def clear
60
+ @hash_of_lists_of_steps.clear
61
+ end
62
+
63
+ def empty?
64
+ [:given_scenario, :given, :when, :then].each do |type|
65
+ return false unless @hash_of_lists_of_steps[type].empty?
66
+ end
67
+ return true
68
+ end
69
+
70
+ def add_to(other_step_matchers)
71
+ [:given_scenario, :given, :when, :then].each do |type|
72
+ other_step_matchers.add(type, @hash_of_lists_of_steps[type])
73
+ end
74
+ end
75
+
76
+ def <<(other_step_matchers)
77
+ other_step_matchers.add_to(self) if other_step_matchers.respond_to?(:add_to)
78
+ end
79
+
80
+ # TODO - make me private
81
+ def create_matcher(type, name, &block)
82
+ matcher = Step.new(name, &block)
83
+ @hash_of_lists_of_steps[type] << matcher
84
+ matcher
85
+ end
86
+
87
+ end
88
+ end
89
+ end