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.
- data/History.txt +5 -0
- data/License.txt +22 -0
- data/Manifest.txt +78 -0
- data/README.txt +23 -0
- data/Rakefile +87 -0
- data/init.rb +4 -0
- data/lib/spec/runner/formatter/story/html_formatter.rb +174 -0
- data/lib/spec/runner/formatter/story/plain_text_formatter.rb +194 -0
- data/lib/spec/runner/formatter/story/progress_bar_formatter.rb +42 -0
- data/lib/spec/runner/options_extensions.rb +25 -0
- data/lib/spec/stories.rb +11 -0
- data/lib/spec/story/extensions.rb +3 -0
- data/lib/spec/story/extensions/main.rb +86 -0
- data/lib/spec/story/extensions/regexp.rb +9 -0
- data/lib/spec/story/extensions/string.rb +9 -0
- data/lib/spec/story/given_scenario.rb +14 -0
- data/lib/spec/story/runner.rb +57 -0
- data/lib/spec/story/runner/plain_text_story_runner.rb +48 -0
- data/lib/spec/story/runner/scenario_collector.rb +18 -0
- data/lib/spec/story/runner/scenario_runner.rb +54 -0
- data/lib/spec/story/runner/story_mediator.rb +137 -0
- data/lib/spec/story/runner/story_parser.rb +247 -0
- data/lib/spec/story/runner/story_runner.rb +74 -0
- data/lib/spec/story/scenario.rb +14 -0
- data/lib/spec/story/step.rb +70 -0
- data/lib/spec/story/step_group.rb +89 -0
- data/lib/spec/story/step_mother.rb +38 -0
- data/lib/spec/story/story.rb +39 -0
- data/lib/spec/story/version.rb +15 -0
- data/lib/spec/story/world.rb +124 -0
- data/resources/rake/verify_rcov.rake +7 -0
- data/rspec-stories.gemspec +35 -0
- data/spec/spec.opts +6 -0
- data/spec/spec/runner/formatter/story/html_formatter_spec.rb +135 -0
- data/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb +600 -0
- data/spec/spec/runner/formatter/story/progress_bar_formatter_spec.rb +82 -0
- data/spec/spec/runner/most_recent_spec.rb +0 -0
- data/spec/spec/runner/options_extensions_spec.rb +31 -0
- data/spec/spec/runner/resources/a_bar.rb +0 -0
- data/spec/spec/runner/resources/a_foo.rb +0 -0
- data/spec/spec/runner/resources/a_spec.rb +1 -0
- data/spec/spec/runner/resources/custom_example_group_runner.rb +14 -0
- data/spec/spec/runner/resources/utf8_encoded.rb +7 -0
- data/spec/spec/runner_spec.rb +11 -0
- data/spec/spec/spec_classes.rb +133 -0
- data/spec/spec/story/builders.rb +46 -0
- data/spec/spec/story/extensions/main_spec.rb +161 -0
- data/spec/spec/story/extensions_spec.rb +14 -0
- data/spec/spec/story/given_scenario_spec.rb +27 -0
- data/spec/spec/story/runner/plain_text_story_runner_spec.rb +90 -0
- data/spec/spec/story/runner/scenario_collector_spec.rb +27 -0
- data/spec/spec/story/runner/scenario_runner_spec.rb +214 -0
- data/spec/spec/story/runner/story_mediator_spec.rb +143 -0
- data/spec/spec/story/runner/story_parser_spec.rb +401 -0
- data/spec/spec/story/runner/story_runner_spec.rb +294 -0
- data/spec/spec/story/runner_spec.rb +93 -0
- data/spec/spec/story/scenario_spec.rb +18 -0
- data/spec/spec/story/step_group_spec.rb +157 -0
- data/spec/spec/story/step_mother_spec.rb +84 -0
- data/spec/spec/story/step_spec.rb +272 -0
- data/spec/spec/story/story_helper.rb +2 -0
- data/spec/spec/story/story_spec.rb +84 -0
- data/spec/spec/story/world_spec.rb +423 -0
- data/spec/spec_helper.rb +84 -0
- data/story_server/prototype/javascripts/builder.js +136 -0
- data/story_server/prototype/javascripts/controls.js +972 -0
- data/story_server/prototype/javascripts/dragdrop.js +976 -0
- data/story_server/prototype/javascripts/effects.js +1117 -0
- data/story_server/prototype/javascripts/prototype.js +4140 -0
- data/story_server/prototype/javascripts/rspec.js +149 -0
- data/story_server/prototype/javascripts/scriptaculous.js +58 -0
- data/story_server/prototype/javascripts/slider.js +276 -0
- data/story_server/prototype/javascripts/sound.js +55 -0
- data/story_server/prototype/javascripts/unittest.js +568 -0
- data/story_server/prototype/lib/server.rb +24 -0
- data/story_server/prototype/stories.html +176 -0
- data/story_server/prototype/stylesheets/rspec.css +136 -0
- data/story_server/prototype/stylesheets/test.css +90 -0
- 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,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
|