macros4cuke 0.3.15 → 0.3.16
Sign up to get free protection for your applications and to get access to all the features.
- data/features/demo06.feature +7 -1
- data/features/step_definitions/demo_steps.rb +17 -0
- data/features/support/env.rb +1 -3
- data/lib/macro_steps.rb +8 -4
- data/lib/macros4cuke/constants.rb +1 -1
- data/lib/macros4cuke/exceptions.rb +10 -5
- data/lib/macros4cuke/macro-step.rb +20 -10
- data/lib/macros4cuke/templating/engine.rb +63 -35
- data/lib/macros4cuke.rb +2 -1
- data/spec/macros4cuke/macro-step-support_spec.rb +6 -6
- data/spec/macros4cuke/macro-step_spec.rb +6 -6
- data/spec/macros4cuke/templating/engine_spec.rb +10 -11
- metadata +1 -1
data/features/demo06.feature
CHANGED
@@ -23,6 +23,11 @@ Scenario: Defining a macro with conditional substeps
|
|
23
23
|
And I click "Save"
|
24
24
|
"""
|
25
25
|
|
26
|
+
|
27
|
+
Scenario: An exception is forced by invoking the above macro with a triple quote string instead of a data table.
|
28
|
+
When I generate a DataTableNotFound exception
|
29
|
+
|
30
|
+
|
26
31
|
Scenario: # Let's use the macro-step WITHOUT the optional argument values.
|
27
32
|
When I [fill in the form with]:
|
28
33
|
|firstname|Alice|
|
@@ -70,4 +75,5 @@ Scenario: # Let's use the macro-step WITH the optional argument values.
|
|
70
75
|
Invoked step: ... I fill in "email" with "alice.inn@wonder.land"
|
71
76
|
Invoked step: ... I fill in "comment" with "No comment!"
|
72
77
|
Invoked step: ... I click "Save"
|
73
|
-
"""
|
78
|
+
"""
|
79
|
+
|
@@ -21,4 +21,21 @@ Then(/^I expect the following step trace:$/) do |step_text|
|
|
21
21
|
end
|
22
22
|
|
23
23
|
|
24
|
+
# This step is used for testing a particular exception
|
25
|
+
When(/^I generate a DataTableNotFound exception$/) do
|
26
|
+
wrong = <<-SNIPPET
|
27
|
+
When I [fill in the form with]:
|
28
|
+
"""
|
29
|
+
Should be a table instead of triple quote string
|
30
|
+
"""
|
31
|
+
SNIPPET
|
32
|
+
begin
|
33
|
+
steps(wrong)
|
34
|
+
rescue Macros4Cuke::DataTableNotFound => exc
|
35
|
+
msg = "The step with phrase [fill in the form with]: requires a data table."
|
36
|
+
exc.message.should == msg
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
24
41
|
# End of file
|
data/features/support/env.rb
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
begin
|
9
9
|
require 'simplecov'
|
10
|
-
rescue LoadError
|
10
|
+
rescue LoadError
|
11
11
|
# Gobble silently the exception if simplecov isn't installed.
|
12
12
|
end
|
13
13
|
|
@@ -43,6 +43,4 @@ end # module
|
|
43
43
|
# making our world object an instance of the TracingWorld class
|
44
44
|
World { DemoMacros4Cuke::TracingWorld.new }
|
45
45
|
|
46
|
-
|
47
|
-
|
48
46
|
# End of file
|
data/lib/macro_steps.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
# File: macro_steps.rb
|
2
|
-
# Purpose: step definitions that help to build macro-steps
|
3
|
+
# Purpose: step definitions that help to build macro-steps
|
4
|
+
# (i.e. a step that is equivalent to a sequence of steps)
|
3
5
|
|
4
6
|
|
5
7
|
|
@@ -13,7 +15,8 @@
|
|
13
15
|
# And I fill in "Password" with "unguessable"
|
14
16
|
# And I click "Submit"
|
15
17
|
# """
|
16
|
-
# The regexp has two capturing group: one for the phrase,
|
18
|
+
# The regexp has two capturing group: one for the phrase,
|
19
|
+
# a second for the terminating colon (:)
|
17
20
|
Given(/^I define the step "(?:Given|When|Then|\*) I \[((?:[^\\\]]|\\.)+)\](:?)" to mean:$/) do |macro_phrase, colon_capture, template|
|
18
21
|
use_table = (colon_capture == ':')
|
19
22
|
add_macro(macro_phrase, template, use_table)
|
@@ -38,11 +41,12 @@ end
|
|
38
41
|
When(/^I \[([^\]]+)\]:$/) do |macro_phrase, table_argument|
|
39
42
|
# Ensure that the second argument is of the correct type
|
40
43
|
unless table_argument.kind_of?(Cucumber::Ast::Table)
|
41
|
-
|
44
|
+
raise Macros4Cuke::DataTableNotFound.new(macro_phrase)
|
42
45
|
end
|
43
46
|
|
44
47
|
# This will call the macro with the given phrase.
|
45
|
-
# The second argument consists of an array
|
48
|
+
# The second argument consists of an array
|
49
|
+
# with couples of the kind: [argument name, actual value]
|
46
50
|
invoke_macro(macro_phrase, table_argument.raw)
|
47
51
|
end
|
48
52
|
|
@@ -30,7 +30,8 @@ end # class
|
|
30
30
|
# and that argument name does not appear in any sub-step.
|
31
31
|
class UnreachableSubstepArgument < Macros4CukeError
|
32
32
|
def initialize(anArgName)
|
33
|
-
|
33
|
+
msg = "The sub-step argument '#{anArgName}' does not appear in the phrase."
|
34
|
+
super(msg)
|
34
35
|
end
|
35
36
|
end # class
|
36
37
|
|
@@ -46,8 +47,9 @@ end # class
|
|
46
47
|
|
47
48
|
# Raised when an argument name contains invalid characters.
|
48
49
|
class InvalidCharError < Macros4CukeError
|
49
|
-
def initialize(
|
50
|
-
|
50
|
+
def initialize(aTag, aWrongChar)
|
51
|
+
msg = "The invalid sign '#{aWrongChar}' occurs in the argument '#{aTag}'."
|
52
|
+
super(msg)
|
51
53
|
end
|
52
54
|
end # class
|
53
55
|
|
@@ -73,11 +75,14 @@ end # class
|
|
73
75
|
|
74
76
|
# Raised when one invokes a macro-step without a required data table argument
|
75
77
|
class DataTableNotFound < Macros4CukeError
|
76
|
-
def initialize(
|
77
|
-
|
78
|
+
def initialize(phrase)
|
79
|
+
msg = "The step with phrase [#{phrase}]: requires a data table."
|
80
|
+
super(msg)
|
78
81
|
end
|
79
82
|
end # class
|
80
83
|
|
84
|
+
msg =
|
85
|
+
|
81
86
|
|
82
87
|
|
83
88
|
# Raised when Macros4Cuke encountered an issue
|
@@ -12,7 +12,8 @@ module Macros4Cuke # Module used as a namespace
|
|
12
12
|
# an aggregation of lower-level sub-steps.
|
13
13
|
# When a macro-step is used in a scenario, then its execution is equivalent
|
14
14
|
# to the execution of its sub-steps.
|
15
|
-
# A macro-step may have zero or more arguments.
|
15
|
+
# A macro-step may have zero or more arguments.
|
16
|
+
# The actual values bound to these arguments
|
16
17
|
# are passed to the sub-steps at execution time.
|
17
18
|
class MacroStep
|
18
19
|
# A template engine that expands the sub-steps upon request.
|
@@ -24,14 +25,18 @@ class MacroStep
|
|
24
25
|
# The list of macro arguments that appears in the macro phrase.
|
25
26
|
attr_reader(:phrase_args)
|
26
27
|
|
27
|
-
# The list of macro argument names (as appearing in the substeps
|
28
|
+
# The list of macro argument names (as appearing in the substeps
|
29
|
+
# and in the macro phrase).
|
28
30
|
attr_reader(:args)
|
29
31
|
|
30
32
|
|
31
33
|
# Constructor.
|
32
|
-
# @param aMacroPhrase[String] The text from the macro step definition
|
33
|
-
#
|
34
|
-
# @param
|
34
|
+
# @param aMacroPhrase[String] The text from the macro step definition
|
35
|
+
# that is between the square brackets.
|
36
|
+
# @param theSubsteps [String] The source text of the steps to be expanded
|
37
|
+
# upon macro invokation.
|
38
|
+
# @param useTable [boolean] A flag indicating whether a data table
|
39
|
+
# must be used to pass actual values.
|
35
40
|
def initialize(aMacroPhrase, theSubsteps, useTable)
|
36
41
|
@key = self.class.macro_key(aMacroPhrase, useTable, :definition)
|
37
42
|
|
@@ -54,19 +59,23 @@ class MacroStep
|
|
54
59
|
# Compute the identifier of the macro from the given macro phrase.
|
55
60
|
# A macro phrase is a text that may contain zero or more placeholders.
|
56
61
|
# In definition mode, a placeholder is delimited by chevrons <..>.
|
57
|
-
# In invokation mode, a value bound to a placeholder is delimited
|
62
|
+
# In invokation mode, a value bound to a placeholder is delimited
|
63
|
+
# by double quotes.
|
58
64
|
# The rule for building the identifying key are:
|
59
65
|
# - Leading and trailing space(s) are removed.
|
60
66
|
# - Each underscore character is removed.
|
61
67
|
# - Every sequence of one or more space(s) is converted into an underscore
|
62
|
-
# - Each placeholder (i.e. = delimiters + enclosed text)
|
68
|
+
# - Each placeholder (i.e. = delimiters + enclosed text)
|
69
|
+
# is converted into a letter X.
|
63
70
|
# - when useTable is true, concatenate: _T
|
64
71
|
# @example:
|
65
72
|
# Consider the macro phrase: 'create the following "contactType" contact'
|
66
73
|
# The resulting macro_key is: 'create_the_following_X_contact_T'
|
67
74
|
#
|
68
|
-
# @param aMacroPhrase [String] The text from the macro step definition
|
69
|
-
#
|
75
|
+
# @param aMacroPhrase [String] The text from the macro step definition
|
76
|
+
# that is between the square brackets.
|
77
|
+
# @param useTable [boolean] A flag indicating whether a table
|
78
|
+
# should be used to pass actual values.
|
70
79
|
# @param mode [:definition, :invokation]
|
71
80
|
# @return [String] the key of the phrase/macro.
|
72
81
|
def self.macro_key(aMacroPhrase, useTable, mode)
|
@@ -79,7 +88,8 @@ class MacroStep
|
|
79
88
|
stripped_phrase.gsub!(/\s+/, '_')
|
80
89
|
|
81
90
|
|
82
|
-
# Determine the pattern to isolate
|
91
|
+
# Determine the pattern to isolate
|
92
|
+
# each argument/parameter with its delimiters
|
83
93
|
pattern = case mode
|
84
94
|
when :definition
|
85
95
|
/<(?:[^\\<>]|\\.)*>/
|
@@ -15,13 +15,15 @@ module Templating
|
|
15
15
|
|
16
16
|
# Class used internally by the template engine.
|
17
17
|
# Represents a static piece of text from a template.
|
18
|
-
# A static text is a text that is reproduced verbatim
|
18
|
+
# A static text is a text that is reproduced verbatim
|
19
|
+
# when rendering a template.
|
19
20
|
class StaticText
|
20
21
|
# The static text extracted from the original template.
|
21
22
|
attr_reader(:source)
|
22
23
|
|
23
24
|
|
24
|
-
# @param aSourceText [String] A piece of text extracted
|
25
|
+
# @param aSourceText [String] A piece of text extracted
|
26
|
+
# from the template that must be rendered verbatim.
|
25
27
|
def initialize(aSourceText)
|
26
28
|
@source = aSourceText
|
27
29
|
end
|
@@ -64,7 +66,8 @@ class UnaryElement
|
|
64
66
|
|
65
67
|
protected
|
66
68
|
# This method has the same signature as the {Engine#render} method.
|
67
|
-
# @return [Object] The actual value from the locals or context
|
69
|
+
# @return [Object] The actual value from the locals or context
|
70
|
+
# that is assigned to the variable.
|
68
71
|
def retrieve_value_from(aContextObject, theLocals)
|
69
72
|
actual_value = theLocals[name]
|
70
73
|
if actual_value.nil? && aContextObject.respond_to?(name.to_sym)
|
@@ -81,7 +84,8 @@ end # class
|
|
81
84
|
# Class used internally by the template engine.
|
82
85
|
# Represents a named placeholder in a template, that is,
|
83
86
|
# a name placed between <..> in the template.
|
84
|
-
# At rendition, a placeholder is replaced by the text value
|
87
|
+
# At rendition, a placeholder is replaced by the text value
|
88
|
+
# that is associated with it.
|
85
89
|
class Placeholder < UnaryElement
|
86
90
|
|
87
91
|
public
|
@@ -155,7 +159,8 @@ public
|
|
155
159
|
# @return [String] The text value assigned to the placeholder.
|
156
160
|
# Returns an empty string when no value is assigned to the placeholder.
|
157
161
|
def render(aContextObject, theLocals)
|
158
|
-
|
162
|
+
msg = "Method Section.#{__method__} must be implemented in subclass."
|
163
|
+
raise NotImplementedError, msg
|
159
164
|
end
|
160
165
|
|
161
166
|
end # class
|
@@ -165,12 +170,14 @@ end # class
|
|
165
170
|
# a set of template elements for which its rendition depends
|
166
171
|
# on the (in)existence of an actual value bound to the variable name.
|
167
172
|
class ConditionalSection < Section
|
168
|
-
# A boolean that indicates whether the rendition condition is
|
173
|
+
# A boolean that indicates whether the rendition condition is
|
174
|
+
# the existence of a value for the variable (true)
|
169
175
|
# or its inexistence (false).
|
170
176
|
attr_reader(:existence)
|
171
177
|
|
172
178
|
# @param aVarName [String] The name of the placeholder from a template.
|
173
|
-
# @param renderWhenExisting [boolean] When true, render the children elements
|
179
|
+
# @param renderWhenExisting [boolean] When true, render the children elements
|
180
|
+
# if a value exists for the variable.
|
174
181
|
def initialize(aVarName, renderWhenExisting = true)
|
175
182
|
super(aVarName)
|
176
183
|
@existence = renderWhenExisting
|
@@ -208,16 +215,22 @@ SectionEndMarker = Struct.new(:name)
|
|
208
215
|
|
209
216
|
|
210
217
|
# A very simple implementation of a templating engine.
|
211
|
-
# Earlier versions of Macros4Cuke relied on the logic-less
|
212
|
-
#
|
218
|
+
# Earlier versions of Macros4Cuke relied on the logic-less
|
219
|
+
# Mustache template engine.
|
220
|
+
# But it was decided afterwards to replace it by a very simple
|
221
|
+
# template engine.
|
213
222
|
# The reasons were the following:
|
214
|
-
# - Be closer to the usual Gherkin syntax (parameters of scenario outlines
|
223
|
+
# - Be closer to the usual Gherkin syntax (parameters of scenario outlines
|
224
|
+
# use chevrons <...>,
|
215
225
|
# while Mustache use !{{...}} delimiters),
|
216
226
|
# - Feature files are meant to be simple, so should the template engine be.
|
217
227
|
class Engine
|
218
|
-
# The regular expression that matches a space,
|
228
|
+
# The regular expression that matches a space,
|
229
|
+
# any punctuation sign or delimiter that is forbidden
|
230
|
+
# between chevrons <...> template tags.
|
219
231
|
DisallowedSigns = begin
|
220
|
-
|
232
|
+
# Use concatenation (+) to work around Ruby bug!
|
233
|
+
forbidden = ' !"#' + "$%&'()*+,-./:;<=>?[\\]^`{|}~"
|
221
234
|
all_escaped = []
|
222
235
|
forbidden.each_char() { |ch| all_escaped << Regexp.escape(ch) }
|
223
236
|
pattern = all_escaped.join("|")
|
@@ -227,19 +240,25 @@ class Engine
|
|
227
240
|
# The original text of the template is kept here.
|
228
241
|
attr_reader(:source)
|
229
242
|
|
230
|
-
# Builds an Engine and compiles the given template text into
|
231
|
-
#
|
243
|
+
# Builds an Engine and compiles the given template text into
|
244
|
+
# an internal representation.
|
245
|
+
# @param aSourceTemplate [String] The template source text.
|
246
|
+
# It may contain zero or tags enclosed between chevrons <...>.
|
232
247
|
def initialize(aSourceTemplate)
|
233
248
|
@source = aSourceTemplate
|
234
249
|
@representation = compile(aSourceTemplate)
|
235
250
|
end
|
236
251
|
|
237
252
|
public
|
238
|
-
# Render the template within the given scope object and
|
253
|
+
# Render the template within the given scope object and
|
254
|
+
# with the locals specified.
|
239
255
|
# The method mimicks the signature of the Tilt::Template#render method.
|
240
|
-
# @param aContextObject [anything] context object to get actual values
|
241
|
-
#
|
242
|
-
# @
|
256
|
+
# @param aContextObject [anything] context object to get actual values
|
257
|
+
# (when not present in the locals Hash).
|
258
|
+
# @param theLocals [Hash] Contains one or more pairs of the form:
|
259
|
+
# tag/placeholder name => actual value.
|
260
|
+
# @return [String] The rendition of the template given
|
261
|
+
# the passed argument values.
|
243
262
|
def render(aContextObject = Object.new, theLocals)
|
244
263
|
return '' if @representation.empty?
|
245
264
|
|
@@ -285,7 +304,9 @@ public
|
|
285
304
|
until scanner.eos?
|
286
305
|
# Scan tag at current position...
|
287
306
|
tag_literal = scanner.scan(/<(?:[^\\<>]|\\.)*>/)
|
288
|
-
|
307
|
+
unless tag_literal.nil?
|
308
|
+
result << [:dynamic, tag_literal.gsub(/^<|>$/, '')]
|
309
|
+
end
|
289
310
|
|
290
311
|
# ... or scan plain text at current position
|
291
312
|
text_literal = scanner.scan(/(?:[^\\<>]|\\.)+/)
|
@@ -303,11 +324,14 @@ private
|
|
303
324
|
def self.identify_parse_error(aTextLine)
|
304
325
|
# Unsuccessful scanning: we typically have improperly balanced chevrons.
|
305
326
|
# We will analyze the opening and closing chevrons...
|
306
|
-
|
307
|
-
|
327
|
+
# First: replace escaped chevron(s)
|
328
|
+
no_escaped = aTextLine.gsub(/\\[<>]/, "--")
|
329
|
+
|
330
|
+
# var. equals count_of(<) - count_of(>): can only be 0 or temporarily 1
|
331
|
+
unbalance = 0
|
308
332
|
|
309
|
-
no_escaped.
|
310
|
-
case
|
333
|
+
no_escaped.each_char do |ch|
|
334
|
+
case ch
|
311
335
|
when '<'
|
312
336
|
unbalance += 1
|
313
337
|
when '>'
|
@@ -318,8 +342,7 @@ private
|
|
318
342
|
raise StandardError, "Missing opening chevron '<'." if unbalance < 0
|
319
343
|
end
|
320
344
|
|
321
|
-
raise StandardError, "Missing closing chevron '>'." if unbalance == 1
|
322
|
-
raise StandardError, "Cannot parse:\n'#{aTextLine}'"
|
345
|
+
raise StandardError, "Missing closing chevron '>'." if unbalance == 1
|
323
346
|
end
|
324
347
|
|
325
348
|
|
@@ -333,7 +356,9 @@ private
|
|
333
356
|
line_items = self.class.parse(line)
|
334
357
|
line_items.each do |(kind, text)|
|
335
358
|
# A tag text cannot be empty nor blank
|
336
|
-
|
359
|
+
if (kind == :dynamic) && text.strip.empty?
|
360
|
+
raise EmptyArgumentError.new(line.strip)
|
361
|
+
end
|
337
362
|
end
|
338
363
|
|
339
364
|
line_items
|
@@ -343,12 +368,13 @@ private
|
|
343
368
|
return compile_sections(compiled_lines.flatten())
|
344
369
|
end
|
345
370
|
|
346
|
-
# Convert the array of raw entries (per line)
|
371
|
+
# Convert the array of raw entries (per line)
|
372
|
+
# into full-fledged template elements.
|
347
373
|
def compile_line(aRawLine)
|
348
374
|
line_rep = aRawLine.map { |couple| compile_couple(couple) }
|
349
375
|
|
350
|
-
# Apply the rule: when a line just consist of spaces
|
351
|
-
# then remove all the spaces from that line.
|
376
|
+
# Apply the rule: when a line just consist of spaces
|
377
|
+
# and a section element, then remove all the spaces from that line.
|
352
378
|
section_item = nil
|
353
379
|
line_to_squeeze = line_rep.all? do |item|
|
354
380
|
case item
|
@@ -370,7 +396,8 @@ private
|
|
370
396
|
line_rep = [section_item]
|
371
397
|
else
|
372
398
|
# Apply another rule: if last item in line is an end of section marker,
|
373
|
-
# then place eoline before that item.
|
399
|
+
# then place eoline before that item.
|
400
|
+
# Otherwise, end the line with a eoline marker.
|
374
401
|
if line_rep.last.is_a?(SectionEndMarker)
|
375
402
|
section_end = line_rep.pop()
|
376
403
|
line_rep << EOLine.new
|
@@ -395,8 +422,6 @@ private
|
|
395
422
|
|
396
423
|
when :dynamic
|
397
424
|
parse_tag(text)
|
398
|
-
else
|
399
|
-
raise StandardError, "Internal error: Don't know template element of kind #{kind}"
|
400
425
|
end
|
401
426
|
|
402
427
|
return result
|
@@ -414,7 +439,6 @@ private
|
|
414
439
|
end
|
415
440
|
raise InvalidCharError.new(aText, matching[0]) if matching
|
416
441
|
|
417
|
-
SectionEndMarker
|
418
442
|
result = case aText[0, 1]
|
419
443
|
when '?'
|
420
444
|
ConditionalSection.new(aText[1..-1], true)
|
@@ -443,7 +467,8 @@ private
|
|
443
467
|
raise StandardError, "End of section</#{element.name}> found while no corresponding section is open."
|
444
468
|
end
|
445
469
|
if element.name != open_sections.last.name
|
446
|
-
|
470
|
+
msg = "End of section</#{element.name}> doesn't match current section '#{open_sections.last.name}'."
|
471
|
+
raise StandardError, msg
|
447
472
|
end
|
448
473
|
subResult << open_sections.pop()
|
449
474
|
|
@@ -457,7 +482,10 @@ private
|
|
457
482
|
|
458
483
|
end
|
459
484
|
|
460
|
-
|
485
|
+
unless open_sections.empty?
|
486
|
+
error_message = "Unterminated section #{open_sections.last}."
|
487
|
+
raise StandardError, error_message
|
488
|
+
end
|
461
489
|
|
462
490
|
return compiled
|
463
491
|
end
|
data/lib/macros4cuke.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8 -- You should see a paragraph character: §
|
2
2
|
# File: macros4cuke.rb
|
3
|
-
# This file acts as a jumping-off point for loading dependencies expected
|
3
|
+
# This file acts as a jumping-off point for loading dependencies expected
|
4
|
+
# for a Macros4Cuke user.
|
4
5
|
|
5
6
|
require_relative './macros4cuke/constants'
|
6
7
|
require_relative './macros4cuke/macro-step-support'
|
@@ -50,8 +50,8 @@ SNIPPET
|
|
50
50
|
it "should complain when entering the same macro again" do
|
51
51
|
# Error case: trying to register another macro with same key/phrase.
|
52
52
|
msg = "A macro-step with phrase 'enter the credentials' already exist."
|
53
|
-
-> { world.add_macro(m1_phrase, m1_substeps, true) }.should
|
54
|
-
|
53
|
+
-> { world.add_macro(m1_phrase, m1_substeps, true) }.should raise_error(
|
54
|
+
Macros4Cuke::DuplicateMacroError, msg)
|
55
55
|
end
|
56
56
|
|
57
57
|
it "should complain macro uses no table and phrase is parameterless" do
|
@@ -59,8 +59,8 @@ SNIPPET
|
|
59
59
|
# but the macro has no mechanism to pass the needed data.
|
60
60
|
phrase = "fill in the credentials"
|
61
61
|
msg = "The sub-step argument 'userid' does not appear in the phrase."
|
62
|
-
->(){ world.add_macro(phrase, m1_substeps, false) }.should
|
63
|
-
|
62
|
+
->(){ world.add_macro(phrase, m1_substeps, false) }.should raise_error(
|
63
|
+
Macros4Cuke::UnreachableSubstepArgument, msg)
|
64
64
|
end
|
65
65
|
end # context
|
66
66
|
|
@@ -69,8 +69,8 @@ SNIPPET
|
|
69
69
|
it "should complain when invoking an unknown macro-step" do
|
70
70
|
phrase_unknown = "dream of a perfect world"
|
71
71
|
msg = "Unknown macro-step with phrase: 'dream of a perfect world'."
|
72
|
-
->(){ world.invoke_macro(phrase_unknown) }.should
|
73
|
-
|
72
|
+
->(){ world.invoke_macro(phrase_unknown) }.should raise_error(
|
73
|
+
Macros4Cuke::UnknownMacroError, msg)
|
74
74
|
end
|
75
75
|
|
76
76
|
end # context
|
@@ -36,16 +36,16 @@ end
|
|
36
36
|
|
37
37
|
it "should complain when a sub-step argument can never be assigned a value via the phrase" do
|
38
38
|
msg = "The sub-step argument 'password' does not appear in the phrase."
|
39
|
-
->(){ MacroStep.new(sample_phrase, sample_template, false) }.should
|
40
|
-
|
39
|
+
->(){ MacroStep.new(sample_phrase, sample_template, false) }.should raise_error(
|
40
|
+
Macros4Cuke::UnreachableSubstepArgument, msg)
|
41
41
|
end
|
42
42
|
|
43
43
|
|
44
44
|
it "should complain when an argument in phrase never occurs in substeps" do
|
45
45
|
phrase = "enter my credentials as <foobar>"
|
46
46
|
msg = "The phrase argument 'foobar' does not appear in a sub-step."
|
47
|
-
->(){ MacroStep.new(phrase, sample_template, true) }.should
|
48
|
-
|
47
|
+
->(){ MacroStep.new(phrase, sample_template, true) }.should raise_error(
|
48
|
+
Macros4Cuke::UselessPhraseArgument, msg)
|
49
49
|
end
|
50
50
|
|
51
51
|
|
@@ -114,8 +114,8 @@ SNIPPET
|
|
114
114
|
# Error case: there is no macro argument called <unknown>
|
115
115
|
error_message = "Unknown macro-step argument 'unknown'."
|
116
116
|
args = [ %w(unknown anything) ]
|
117
|
-
->(){ subject.expand(phrase_instance, args) }.should
|
118
|
-
|
117
|
+
->(){ subject.expand(phrase_instance, args) }.should raise_error(
|
118
|
+
UnknownArgumentError, error_message)
|
119
119
|
end
|
120
120
|
|
121
121
|
end # context
|
@@ -210,15 +210,15 @@ SNIPPET
|
|
210
210
|
it "should complain when a placeholder is empty or blank" do
|
211
211
|
text_w_empty_arg = sample_template.sub(/userid/, '')
|
212
212
|
msg = %q(An empty or blank argument occurred in 'And I fill in "Username" with "<>"'.)
|
213
|
-
->(){ Engine.new text_w_empty_arg }.should
|
214
|
-
|
213
|
+
->(){ Engine.new text_w_empty_arg }.should raise_error(
|
214
|
+
Macros4Cuke::EmptyArgumentError, msg)
|
215
215
|
end
|
216
216
|
|
217
217
|
it "should complain when a placeholder contains an invalid character" do
|
218
218
|
text_w_empty_arg = sample_template.sub(/userid/, 'user%id')
|
219
|
-
msg = "The invalid sign '%' occurs in the argument
|
220
|
-
->(){ Engine.new text_w_empty_arg }.should
|
221
|
-
|
219
|
+
msg = "The invalid sign '%' occurs in the argument 'user%id'."
|
220
|
+
->(){ Engine.new text_w_empty_arg }.should raise_error(
|
221
|
+
Macros4Cuke::InvalidCharError, msg)
|
222
222
|
end
|
223
223
|
|
224
224
|
it "should complain when a section has no closing tag" do
|
@@ -226,8 +226,8 @@ SNIPPET
|
|
226
226
|
text_w_open_section = sophisticated_template.sub(/<\/address>/, '')
|
227
227
|
|
228
228
|
error_message = "Unterminated section <?address>."
|
229
|
-
->(){ Engine.new text_w_open_section}.should
|
230
|
-
|
229
|
+
->(){ Engine.new text_w_open_section}.should raise_error(
|
230
|
+
StandardError, error_message)
|
231
231
|
end
|
232
232
|
|
233
233
|
it "should complain when a closing tag has no corresponding opening tag" do
|
@@ -235,16 +235,15 @@ SNIPPET
|
|
235
235
|
text_w_wrong_end = sophisticated_template.sub(/<\/address>/, '</foobar>')
|
236
236
|
|
237
237
|
msg = "End of section</foobar> doesn't match current section 'address'."
|
238
|
-
->(){ Engine.new text_w_wrong_end }.should
|
239
|
-
|
238
|
+
->(){ Engine.new text_w_wrong_end }.should raise_error(
|
239
|
+
StandardError, msg)
|
240
240
|
end
|
241
241
|
|
242
242
|
it "should complain when a closing tag is found without opening tag" do
|
243
243
|
# Replacing an end of section tag by another...
|
244
244
|
wrong_end = sophisticated_template.sub(/<\?birthdate>/, '</foobar>')
|
245
245
|
msg = "End of section</foobar> found while no corresponding section is open."
|
246
|
-
->(){ Engine.new wrong_end }.should
|
247
|
-
raise_error(StandardError, msg)
|
246
|
+
->(){ Engine.new wrong_end }.should raise_error(StandardError, msg)
|
248
247
|
end
|
249
248
|
|
250
249
|
end # context
|