macros4cuke 0.3.15 → 0.3.16
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/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
|