macros4cuke 0.3.00 → 0.3.01

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.3.01 / 2013-05-10
2
+ * [NEW] features/ folder: added `demo06.feature` file showing the new conditional section.
3
+ * [NEW] File `placeholder_spec.rb`: Added a RSpec file to test the Placeholder class.
4
+ * [CHANGE] File `macro_steps.rb` Macro-step definition accepts the '*' Gherkin keyword.
5
+ * [CHANGE] Method `Engine#compile_sction` completed and tested to support section elements.
6
+ * [CHANGE] Method `Section#variables` expanded to support section elements.
7
+ * [CHANGE] Method `Engine#variables` expanded to support section elements.
8
+ * [CHANGE] Method `Engine#compile_line` added two formatting rules.
9
+ * [CHANGE] Method `MacroStep#scan_arguments` now un-escape the \" sequence into plain quote.
10
+ * [CHANGE] examples/ folder expanded and reorganized
11
+ * [FIX] Method `Section#render` fixed typo in call to __method__
12
+
1
13
  ## 0.3.00 / 2013-05-09 Version number bumped
2
14
  * [CHANGE] Class `Templating::Engine` can handle conditional tags (TODO: document).
3
15
  * [CHANGE] File `engine_spec.rb`: Added a RSpec examples to test the conditional tags.
@@ -0,0 +1,6 @@
1
+ default: --strict -v -b --format progress features
2
+ html: --strict -v -b --format html --out result.html features
3
+ usage: --strict -v -b --format usage --out usage.txt features
4
+
5
+
6
+
@@ -0,0 +1,54 @@
1
+ # File: basic.feature
2
+
3
+ Feature: Show -visually- the several ways to use macros
4
+ As a Cuke user
5
+ So that I enjoy writing scenario.
6
+
7
+
8
+ Scenario: Definition of a simple macro-step with two arguments
9
+ Given I define the step "* I [travel from <origin> to <destination>]" to mean:
10
+ """
11
+ When I leave '<origin>'
12
+ And I arrive in <destination>
13
+ """
14
+
15
+ Scenario: Do a simple travel
16
+ # Call a macro-step defined earlier
17
+ When I [travel from "Brussels" to "Rome"]
18
+ # You should see the output:
19
+ # I leave 'Brussels'
20
+ # I arrive in Rome
21
+
22
+
23
+ # Actual values can have embedded double quotes provided they are escaped.
24
+ When I [travel from "Tampa" to "\"Little Italy\""]
25
+ # You should see the output:
26
+ # I leave 'Tampa'
27
+ # I arrive in "Little Italy"
28
+
29
+ # Actual values MUST be present in the phrase (but they can be empty)
30
+ When I [travel from "" to "North Pole"]
31
+ # You should see the output:
32
+ # I leave ''
33
+ # I arrive in North Pole
34
+
35
+
36
+
37
+ Scenario: Defining a macro that's calling other macro-steps
38
+ Given I define the step "* I [travel from <origin> to <destination> and back]" to mean:
39
+ """
40
+ # The next two steps are, in fact, macro-step invokations
41
+ When I [travel from "<origin>" to "<destination>"]
42
+ When I [travel from "<destination>" to "<origin>"]
43
+ """
44
+
45
+ Scenario: Do a travel back and forth
46
+ When I [travel from "Paris" to "London" and back]
47
+
48
+ # You should see the output:
49
+ # I leave 'Paris'
50
+ # I arrive in London
51
+ # I leave 'London'
52
+ # I arrive in Paris
53
+
54
+
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ # File: demo_steps.rb
3
+ # A few step definitions for demo and testing purpose.
4
+
5
+ When(/^I leave '(.*)'$/) do |city|
6
+ show "I leave #{city}"
7
+ end
8
+
9
+
10
+ When(/^I visit (.+)$/) do |city|
11
+ show "I visit #{city}"
12
+ end
13
+
14
+
15
+ When(/^I arrive in (.+)$/) do |city|
16
+ show "I arrive in #{city}"
17
+ end
18
+
19
+ When(/^I type \"([^"]*)\"$/) do |text|
20
+ show text
21
+ end
22
+
23
+ # End of file
@@ -0,0 +1,8 @@
1
+ # File: use_macro_steps.rb
2
+ # Place a copy of this file in the feature/step_definitions folder of your own Cucumber-based project.
3
+
4
+ # The following require will load the step definitions from Macros4Cuke.
5
+ # This allows feature file authors to use macro steps in their Cucumber scenarios.
6
+ require 'macros4cuke/../macro_steps'
7
+
8
+ # End of file
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8 You should see a paragraph character: §
2
+ # File: env.rb
3
+
4
+
5
+ module DemoMacros4Cuke # Use the module as a namespace
6
+
7
+
8
+ # Class created just for testing and demonstration purposes.
9
+ # Its instance, will record the output emitted by the steps.
10
+ class TracingWorld
11
+ # Will contain the text emitted by the steps
12
+ attr_reader(:trace_steps)
13
+
14
+
15
+ def initialize()
16
+ # Constructor
17
+ @trace_steps = []
18
+ end
19
+
20
+ public
21
+ # Write the given text to the error console
22
+ def show(someText)
23
+ # Replace every \" sequence by genuine "
24
+ unescaped = someText.gsub(/\\"/, '"')
25
+ $stderr.puts(unescaped)
26
+ end
27
+
28
+
29
+ end # class
30
+
31
+ end # module
32
+
33
+ # For testing purpose we override the default Cucumber behaviour
34
+ # making our world object an instance of the TracingWorld class
35
+ World { DemoMacros4Cuke::TracingWorld.new }
36
+
37
+
38
+ # End of file
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8 You should see a paragraph character: §
2
+ # File: macro_support.rb
3
+ # Purpose: Add the support for macros in Cucumber.
4
+ # This file is meant to be put next to the 'env.rb' file of your Cucumeber project.
5
+
6
+
7
+ # Macros4Cuke step one: Load modules and classes from the gem.
8
+ require 'macros4cuke'
9
+
10
+
11
+ # Macros4Cuke step two: extend the world object with the mix-in module
12
+ # that adds the support for macros in Cucumber.
13
+ World(Macros4Cuke::MacroStepSupport)
14
+
15
+
16
+ # End of file
@@ -1,51 +1,14 @@
1
1
  # File: travelling-demo.feature
2
2
 
3
- Feature: Show -visually- the several ways to use macros
3
+
4
+ Feature: Show how to define & use macro-steps with data table
4
5
  As a Cuke user
5
6
  So that I enjoy writing scenario.
6
7
 
7
8
 
8
- Scenario: Definition of a simple macro-step with two arguments
9
- Given I define the step "When I [travel from <origin> to <destination>]" to mean:
10
- """
11
- When I leave <origin>
12
- And I arrive in <destination>
13
- """
14
-
15
- Scenario: Do a simple travel
16
- # Call a macro-step defined earlier
17
- When I [travel from "Brussels" to "Rome"]
18
-
19
- # You should see the output:
20
- # I leave Brussels
21
- # I arrive in Rome
22
-
23
- # Actual values can have embedded double quotes provided they are escaped.
24
- When I [travel from "Tampa" to "\"Little Italy\""]
25
-
26
-
27
-
28
-
29
- Scenario: Defining a macro calling other macro(s)
30
- Given I define the step "When I [travel from <origin> to <destination> and back]" to mean:
31
- """
32
- # The next two steps are, in fact, macro-step invokations
33
- When I [travel from "<origin>" to "<destination>"]
34
- When I [travel from "<destination>" to "<origin>"]
35
- """
36
-
37
- Scenario: Do a travel back and forth
38
- When I [travel from "Paris" to "London" and back]
39
-
40
- # You should see the output:
41
- # I leave Paris
42
- # I arrive in London
43
- # I leave London
44
- # I arrive in Paris
45
-
46
-
47
9
  Scenario: Defining a macro that requires a data table
48
- Given I define the step "When I [fill in the form with]:" to mean:
10
+ # There is a colon : just after the phrase closing ']'. A data table must be used.
11
+ Given I define the step "* I [fill in the form with]:" to mean:
49
12
  """
50
13
  When I type "<firstname>"
51
14
  And I type "<lastname>"
@@ -92,7 +55,7 @@ Scenario: Using a macro-step with a data table
92
55
 
93
56
 
94
57
  Scenario: Demonstrate that it is possible to use a sub-step with a data table
95
- Given I define the step "When I [fill in, as a Londonian, the form with]:" to mean:
58
+ Given I define the step "* I [fill in, as a Londonian, the form with]:" to mean:
96
59
  """
97
60
  When I [fill in the form with]:
98
61
  |firstname| <firstname>|
@@ -9,7 +9,7 @@ Scenario: Definition of a simple macro-step
9
9
  # The next step creates a macro(-step)
10
10
  # The syntax of the new macro-step is specified between double quotes.
11
11
  # The steps to execute when the macro is used/invoked are listed in the multiline triple quotes arguments.
12
- Given I define the step "When I [log in]" to mean:
12
+ Given I define the step "* I [log in]" to mean:
13
13
  """
14
14
  Given I landed in the homepage
15
15
  When I click "Sign in"
@@ -30,4 +30,4 @@ Invoked step: ... I click "Sign in"
30
30
  Invoked step: ... I fill in "Username" with "johndoe"
31
31
  Invoked step: ... I fill in "Password" with "unguessable"
32
32
  Invoked step: ... I click "Submit"
33
- """
33
+ """
@@ -10,7 +10,7 @@ Scenario: Creating a basic scenario with one argument
10
10
  # The syntax of the new macro-step is specified between the double quotes.
11
11
  # The steps to execute when the macro is used/invoked are listed in the multiline triple quotes arguments.
12
12
  # The macro argument is put between chevrons <...>.
13
- Given I define the step "When I [log in\[\] as <userid>]" to mean:
13
+ Given I define the step "* I [log in\[\] as <userid>]" to mean:
14
14
  """
15
15
  Given I landed in the homepage
16
16
  When I click "Sign in"
@@ -31,4 +31,4 @@ Invoked step: ... I click "Sign in"
31
31
  Invoked step: ... I fill in "Username" with "guest"
32
32
  Invoked step: ... I fill in "Password" with "unguessable"
33
33
  Invoked step: ... I click "Submit"
34
- """
34
+ """
@@ -9,7 +9,7 @@ Scenario: defining basic macro with multiple arguments
9
9
  # The next step creates a macro(-step)double quotes.
10
10
  # The steps to execute when the macro is used/invoked are listed in the multiline triple quotes arguments.
11
11
  # The macro-step arguments are put between chevrons <...>.
12
- Given I define the step "When I [enter my userid <userid> and password <password>]" to mean:
12
+ Given I define the step "* I [enter my userid <userid> and password <password>]" to mean:
13
13
  """
14
14
  Given I landed in the homepage
15
15
  When I click "Sign in"
@@ -33,7 +33,7 @@ Invoked step: ... I click "Submit"
33
33
  """
34
34
 
35
35
  Scenario: A macro invoking another macro (YES, it's possible!)
36
- Given I define the step "When I [enter my credentials]" to mean:
36
+ Given I define the step "* I [enter my credentials]" to mean:
37
37
  """
38
38
  # Notice that the next step is invoking the first macro above
39
39
  When I [enter my userid "guest" and password "unguessable"]
@@ -50,4 +50,4 @@ Invoked step: ... I click "Sign in"
50
50
  Invoked step: ... I fill in "Username" with "guest"
51
51
  Invoked step: ... I fill in "Password" with "unguessable"
52
52
  Invoked step: ... I click "Submit"
53
- """
53
+ """
@@ -10,7 +10,7 @@ Scenario: Defining a macro to be used with multiple arguments in a table
10
10
  # The syntax of the new macro-step is specified between double quotes.
11
11
  # The steps to execute when the macro is used/invoked are listed in the multiline triple quotes arguments.
12
12
  # The macro arguments are put between chevrons <...>.
13
- Given I define the step "When I [enter my credentials as]:" to mean:
13
+ Given I define the step "* I [enter my credentials as]:" to mean:
14
14
  """
15
15
  Given I landed in the homepage
16
16
  When I click "Sign in"
@@ -33,4 +33,4 @@ Invoked step: ... I click "Sign in"
33
33
  Invoked step: ... I fill in "Username" with "guest"
34
34
  Invoked step: ... I fill in "Password" with "unguessable"
35
35
  Invoked step: ... I click "Submit"
36
- """
36
+ """
@@ -10,7 +10,7 @@ Scenario: Defining a macro to be used with multiple arguments in a table
10
10
  # The syntax of the new macro-step is specified between double quotes.
11
11
  # The steps to execute when the macro is used/invoked are listed in the multiline triple quotes arguments.
12
12
  # The macro arguments are put between chevrons <...>.
13
- Given I define the step "When I [enter my profile as]:" to mean:
13
+ Given I define the step "* I [enter my profile as]:" to mean:
14
14
  """
15
15
  And I fill in "location" with "<location>"
16
16
  And I fill in "email" with "<email>"
@@ -34,4 +34,4 @@ Scenario: # Let's use the macro we created above
34
34
  Invoked step: ... I fill in "email" with "nobody@example.com"
35
35
  Invoked step: ... I fill in "comment" with "First comment line<br/>Second comment line<br/>Third comment line"
36
36
  Invoked step: ... I click "Save"
37
- """
37
+ """
@@ -0,0 +1,73 @@
1
+ # File: demo06.feature
2
+
3
+ Feature: Show how to define conditional substeps in a macro-step.
4
+ As a Cuke user
5
+ So that I enjoy writing scenario.
6
+
7
+
8
+ Scenario: Defining a macro with conditional substeps
9
+ Given I define the step "* I [fill in the form with]:" to mean:
10
+ """
11
+ When I fill in "first_name" with "<firstname>"
12
+ And I fill in "last_name" with "<lastname>"
13
+ And I fill in "street_address" with "<street_address>"
14
+ And I fill in "zip" with "<postcode>"
15
+ And I fill in "city" with "<city>"
16
+ And I fill in "country" with "<country>"
17
+ # Let's assume that e-mail is optional
18
+ <?email>
19
+ And I fill in "email" with "<email>"
20
+ </email>
21
+ # Let's also assume that comment is optional
22
+ <?comment> And I fill in "comment" with "<comment>"</comment>
23
+ And I click "Save"
24
+ """
25
+
26
+ Scenario: # Let's use the macro-step WITHOUT the optional argument values.
27
+ When I [fill in the form with]:
28
+ |firstname|Alice|
29
+ |lastname| Inn |
30
+ |street_address| 11, No Street|
31
+ |city| Nowhere-City|
32
+ |country|Wonderland|
33
+ # No e-mail
34
+ # No comment
35
+
36
+ # The next step verifies that the optional steps from the macro were ignored.
37
+ Then I expect the following step trace:
38
+ """
39
+ Invoked step: ... I fill in "first_name" with "Alice"
40
+ Invoked step: ... I fill in "last_name" with "Inn"
41
+ Invoked step: ... I fill in "street_address" with "11, No Street"
42
+ Invoked step: ... I fill in "zip" with ""
43
+ Invoked step: ... I fill in "city" with "Nowhere-City"
44
+ Invoked step: ... I fill in "country" with "Wonderland"
45
+ Invoked step: ... I click "Save"
46
+ """
47
+
48
+
49
+ Scenario: # Let's use the macro-step WITH the optional argument values.
50
+ # Redo, now with e-mail and comment
51
+ When I [fill in the form with]:
52
+ |firstname|Alice|
53
+ |lastname| Inn |
54
+ |street_address| 11, No Street|
55
+ |city| Nowhere-City|
56
+ |country|Wonderland|
57
+ # Here come the optional values
58
+ |email|alice.inn@wonder.land|
59
+ |comment|No comment!|
60
+
61
+ # The next step verifies that the optional steps from the macro were ignored.
62
+ Then I expect the following step trace:
63
+ """
64
+ Invoked step: ... I fill in "first_name" with "Alice"
65
+ Invoked step: ... I fill in "last_name" with "Inn"
66
+ Invoked step: ... I fill in "street_address" with "11, No Street"
67
+ Invoked step: ... I fill in "zip" with ""
68
+ Invoked step: ... I fill in "city" with "Nowhere-City"
69
+ Invoked step: ... I fill in "country" with "Wonderland"
70
+ Invoked step: ... I fill in "email" with "alice.inn@wonder.land"
71
+ Invoked step: ... I fill in "comment" with "No comment!"
72
+ Invoked step: ... I click "Save"
73
+ """
@@ -20,23 +20,4 @@ Then(/^I expect the following step trace:$/) do |step_text|
20
20
  end
21
21
 
22
22
 
23
- When(/^I leave (.+)$/) do |city|
24
- show "I leave #{city}"
25
- end
26
-
27
-
28
- When(/^I visit (.+)$/) do |city|
29
- show "I visit #{city}"
30
- end
31
-
32
-
33
- When(/^I arrive in (.+)$/) do |city|
34
- show "I arrive in #{city}"
35
- end
36
-
37
- When(/^I type \"([^"]*)\"$/) do |text|
38
- show text
39
- end
40
-
41
-
42
23
  # End of file
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Macros4Cuke # Module used as a namespace
5
5
  # The version number of the gem.
6
- Version = '0.3.00'
6
+ Version = '0.3.01'
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = "Macros for Cucumber"
@@ -158,11 +158,14 @@ private
158
158
  # /{{{([^}]*)}}}|{{([^}]*)}}/ # Two capturing groups!...
159
159
  when :invokation
160
160
  /"((?:[^\\"]|\\.)*)"/
161
- else
162
- raise InternalError, "Internal error: Unknown mode argument #{mode}"
163
161
  end
164
162
  raw_result = aMacroPhrase.scan(pattern)
165
- return raw_result.flatten.compact
163
+ args = raw_result.flatten.compact
164
+
165
+ # Replace escaped quotes by quote character.
166
+ args.map! { |a| a.sub(/\\"/, '"') } if mode == :invokation
167
+
168
+ return args
166
169
  end
167
170
 
168
171
  # Return the substeps text after some transformation
@@ -204,5 +207,4 @@ end # class
204
207
 
205
208
  end # module
206
209
 
207
-
208
210
  # End of file
@@ -130,14 +130,31 @@ public
130
130
  def add_child(aChild)
131
131
  children << aChild
132
132
  end
133
+
134
+ # Retrieve all placeholder names that appear in the template.
135
+ # @return [Array] The list of placeholder names.
136
+ def variables()
137
+ all_vars = children.each_with_object([]) do |a_child, subResult|
138
+ case a_child
139
+ when Placeholder
140
+ subResult << a_child.name
141
+ when Section
142
+ subResult.concat(a_child.variables)
143
+ else
144
+ # Do nothing
145
+ end
146
+ end
147
+
148
+ return all_vars.flatten.uniq
149
+ end
150
+
133
151
 
134
- protected
135
152
  # Render the placeholder given the passed arguments.
136
153
  # This method has the same signature as the {Engine#render} method.
137
154
  # @return [String] The text value assigned to the placeholder.
138
155
  # Returns an empty string when no value is assigned to the placeholder.
139
156
  def render(aContextObject, theLocals)
140
- raise NotImplementedError, "Method Section::#{_method_} must be implemented in subclass(es)."
157
+ raise NotImplementedError, "Method Section::#{__method__} must be implemented in subclass(es)."
141
158
  end
142
159
 
143
160
  end # class
@@ -175,7 +192,13 @@ public
175
192
  end
176
193
 
177
194
  return result
178
- end
195
+ end
196
+
197
+
198
+ # @return [String] The original text representation of the tag.
199
+ def to_s()
200
+ return "<?#{name}>"
201
+ end
179
202
 
180
203
  end # class
181
204
 
@@ -232,8 +255,20 @@ public
232
255
  def variables()
233
256
  # The result will be cached/memoized...
234
257
  @variables ||= begin
235
- vars = @representation.select { |element| element.is_a?(Placeholder) }
236
- vars.map(&:name)
258
+ vars = @representation.each_with_object([]) do |element, subResult|
259
+ case element
260
+ when Placeholder
261
+ subResult << element.name
262
+
263
+ when Section
264
+ subResult.concat(element.variables)
265
+
266
+ else
267
+ # Do nothing
268
+ end
269
+ end
270
+
271
+ vars.flatten.uniq
237
272
  end
238
273
 
239
274
  return @variables
@@ -302,10 +337,44 @@ private
302
337
  return compile_sections(compiled_lines.flatten())
303
338
  end
304
339
 
305
- # Convert the array of raw entries into full-fledged template elements.
340
+ # Convert the array of raw entries (per line) into full-fledged template elements.
306
341
  def compile_line(aRawLine)
307
342
  line_rep = aRawLine.map { |couple| compile_couple(couple) }
308
- line_rep << EOLine.new
343
+
344
+ # Apply the rule: when a line just consist of spaces and a section element,
345
+ # then remove all the spaces from that line.
346
+ section_item = nil
347
+ line_to_squeeze = line_rep.all? do |item|
348
+ case item
349
+ when StaticText
350
+ item.source =~ /\s+/
351
+
352
+ when Section, SectionEndMarker
353
+ if section_item.nil?
354
+ section_item = item
355
+ true
356
+ else
357
+ false
358
+ end
359
+ else
360
+ false
361
+ end
362
+ end
363
+ if line_to_squeeze && ! section_item.nil?
364
+ line_rep = [section_item]
365
+ else
366
+ # Apply another rule: if last item in line is an end of section marker,
367
+ # then place eoline before that item. Otherwise, end the line with a eoline marker.
368
+ if line_rep.last.is_a?(SectionEndMarker)
369
+ section_end = line_rep.pop()
370
+ line_rep << EOLine.new
371
+ line_rep << section_end
372
+ else
373
+ line_rep << EOLine.new
374
+ end
375
+ end
376
+
377
+ return line_rep
309
378
  end
310
379
 
311
380
 
@@ -353,10 +422,11 @@ private
353
422
  return result
354
423
  end
355
424
 
356
- # Group the elements by sections.
425
+ # Transform a flat sequence of elements into a hierarchy of sections.
357
426
  # @param flat_sequence [Array] a linear list of elements (including sections)
358
427
  def compile_sections(flat_sequence)
359
428
  open_sections = [] # The list of nested open sections
429
+
360
430
  compiled = flat_sequence.each_with_object([]) do |element, subResult|
361
431
  case element
362
432
  when Section
@@ -364,7 +434,7 @@ private
364
434
 
365
435
  when SectionEndMarker
366
436
  if open_sections.empty?
367
- raise StandardError, "End of section</#{element.name}> found while no corresponding section must be closed."
437
+ raise StandardError, "End of section</#{element.name}> found while no corresponding section is open."
368
438
  end
369
439
  if element.name != open_sections.last.name
370
440
  raise StandardError, "End of section</#{element.name}> doesn't match current section '#{open_sections.last.name}'."
@@ -381,6 +451,8 @@ private
381
451
 
382
452
  end
383
453
 
454
+ raise StandardError, "Unterminated section #{open_sections.last}." unless open_sections.empty?
455
+
384
456
  return compiled
385
457
  end
386
458
 
@@ -91,7 +91,22 @@ SNIPPET
91
91
 
92
92
  text.should == expectation
93
93
  end
94
+
95
+ it "should un-escape the double-quotes for phrase arguments" do
96
+ specific_phrase = %q|enter my credentials as "quotable\""|
97
+ text = subject.expand(specific_phrase, [ ['password', 'no-secret'] ])
98
+ expectation = <<-SNIPPET
99
+ Given I landed in the homepage
100
+ When I click "Sign in"
101
+ And I fill in "Username" with "quotable""
102
+ And I fill in "Password" with "no-secret"
103
+ And I click "Submit"
104
+ SNIPPET
105
+
106
+ text.should == expectation
107
+ end
94
108
 
109
+
95
110
  it "should complain when an unknown variable is used" do
96
111
  # Error case: there is no macro argument called <unknown>
97
112
  error_message = "Unknown macro-step argument 'unknown'."
@@ -27,10 +27,11 @@ SNIPPET
27
27
  source = <<-SNIPPET
28
28
  When I fill in "firstname" with "<firstname>"
29
29
  And I fill in "lastname" with "<lastname>"
30
- <?address>And I fill in "address" with "<address>"</address>
30
+ <?address> And I fill in "address" with "<address>"</address>
31
31
  <?birthdate>
32
32
  And I fill in "birthdate" with "<birthdate>"
33
33
  </birthdate>
34
+ <?dummy></dummy>
34
35
  And I click "Register"
35
36
  SNIPPET
36
37
  end
@@ -201,6 +202,29 @@ SNIPPET
201
202
  lambda { Engine.new text_w_empty_arg }.should raise_error(Macros4Cuke::InvalidCharError, error_message)
202
203
  end
203
204
 
205
+ it "should complain when a section has no closing tag" do
206
+ # Removing an end of section tag...
207
+ text_w_open_section = sophisticated_template.sub(/<\/address>/, '')
208
+
209
+ error_message = "Unterminated section <?address>."
210
+ lambda { Engine.new text_w_open_section}.should raise_error(StandardError, error_message)
211
+ end
212
+
213
+ it "should complain when a closing tag does not correspond to currently open section" do
214
+ # Replacing an end of section tag by another...
215
+ text_w_wrong_end = sophisticated_template.sub(/<\/address>/, '</foobar>')
216
+
217
+ error_message = "End of section</foobar> doesn't match current section 'address'."
218
+ lambda { Engine.new text_w_wrong_end}.should raise_error(StandardError, error_message)
219
+ end
220
+
221
+ it "should complain when a closing tag is found while no section is open" do
222
+ # Replacing an end of section tag by another...
223
+ text_w_wrong_end = sophisticated_template.sub(/<\?birthdate>/, '</foobar>')
224
+ error_message = "End of section</foobar> found while no corresponding section is open."
225
+ lambda { Engine.new text_w_wrong_end}.should raise_error(StandardError, error_message)
226
+ end
227
+
204
228
  end # context
205
229
 
206
230
  context "Provided services" do
@@ -283,15 +307,25 @@ SNIPPET
283
307
  expected = <<-SNIPPET
284
308
  When I fill in "firstname" with "Anon"
285
309
  And I fill in "lastname" with "Eemoos"
286
-
287
-
288
310
  And I fill in "birthdate" with "1976-04-21"
289
-
290
311
  And I click "Register"
291
312
  SNIPPET
292
313
 
293
314
  rendered_text.should == expected
294
315
 
316
+ # Redo with another context
317
+ locals["birthdate"] = nil
318
+ locals["address"] = "122, Mercer Street"
319
+
320
+ rendered_text = instance.render(Object.new, locals)
321
+ expected = <<-SNIPPET
322
+ When I fill in "firstname" with "Anon"
323
+ And I fill in "lastname" with "Eemoos"
324
+ And I fill in "address" with "122, Mercer Street"
325
+ And I click "Register"
326
+ SNIPPET
327
+
328
+ rendered_text.should == expected
295
329
  end
296
330
 
297
331
 
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8 -- You should see a paragraph character: §
2
+ # File: section_spec.rb
3
+
4
+ require_relative '../../spec_helper'
5
+ require_relative '../../../lib/macros4cuke/templating/engine' # Load the classes under test
6
+
7
+ module Macros4Cuke
8
+
9
+ module Templating # Open this namespace to get rid of module qualifier prefixes
10
+
11
+
12
+ describe Placeholder do
13
+ # Default instantiation rule
14
+ subject { Placeholder.new('foobar') }
15
+
16
+ context "Creation and initialization:" do
17
+
18
+ it "should be created with a variable name" do
19
+ lambda { Placeholder.new('foobar') }.should_not raise_error
20
+ end
21
+
22
+ it "should know the name of its variable" do
23
+ subject.name.should == 'foobar'
24
+ end
25
+
26
+ end # context
27
+
28
+ context "Provided services:" do
29
+ it "should render an empty string when no actual value is bound to the placeholder" do
30
+ # Case: context has no value associated to 'foobar'
31
+ rendered_text = subject.render(Object.new, {})
32
+ rendered_text.should be_empty
33
+
34
+ # Case: locals Hash has a nil value associated to 'foobar'
35
+ rendered_text = subject.render(Object.new, {'foobar' => nil})
36
+ rendered_text.should be_empty
37
+
38
+ # Case: context object has a nil value associated to 'foobar'
39
+ context = Object.new
40
+ def context.foobar; nil; end # Add singleton method foobar that returns nil
41
+ rendered_text = subject.render(context, {})
42
+ rendered_text.should be_empty
43
+ end
44
+
45
+ it "should render the actual value bound to the placeholder" do
46
+ # Case: locals Hash has a value associated to 'foobar'
47
+ rendered_text = subject.render(Object.new, {'foobar' => 'hello'})
48
+ rendered_text.should == 'hello'
49
+
50
+ # Case: context object has a value associated to 'foobar'
51
+ context = Object.new
52
+ def context.foobar; 'world'; end # Add singleton method foobar that returns 'world'
53
+ rendered_text = subject.render(context, {})
54
+ rendered_text.should == 'world'
55
+ end
56
+
57
+ end # context
58
+
59
+ end # describe
60
+
61
+ end # module
62
+
63
+ end # module
64
+
65
+ # End of file
@@ -8,23 +8,85 @@ module Macros4Cuke
8
8
 
9
9
  module Templating # Open this namespace to get rid of module qualifier prefixes
10
10
 
11
+ # Spec for abstract class Section.
12
+ describe Section do
13
+ # Default instantiation rule
14
+ subject { Section.new('foobar') }
15
+
16
+ # Return a list of possible child elements
17
+ let(:sample_children) do
18
+ [ StaticText.new("Hello "),
19
+ Placeholder.new("user"),
20
+ EOLine.new
21
+ ]
22
+ end
23
+
24
+ context "Creation and initialization" do
25
+
26
+ it "should be created with a variable name" do
27
+ lambda { Section.new('foobar') }.should_not raise_error
28
+ end
29
+
30
+ it "should know the name of its variable" do
31
+ subject.name.should == 'foobar'
32
+ end
33
+
34
+ it "should have no child at start" do
35
+ subject.should have(0).children
36
+ end
37
+
38
+ end # context
39
+
40
+ context "Provided services:" do
41
+ it "should add child element(s)" do
42
+ sample_children.each do |a_child|
43
+ subject.add_child(a_child)
44
+ end
45
+
46
+ # Control that the addition work as expected
47
+ subject.children.should == sample_children
48
+ end
49
+
50
+ it "should know the name all child placeholders" do
51
+ # Case: simple flat list of children
52
+ sample_children.each { |a_child| subject.add_child(a_child) }
53
+ subject.variables.should == [ 'user' ]
54
+
55
+ # Case: at least one child is a group
56
+ parent = Section.new('son')
57
+ [
58
+ subject,
59
+ StaticText.new("Bye "),
60
+ Placeholder.new("firstname"),
61
+ EOLine.new
62
+ ].each { |a_child| parent.add_child(a_child) }
63
+
64
+ parent.variables.should == [ 'user', 'firstname']
65
+
66
+ end
67
+
68
+
69
+ it "should expect that its subclasses render the children" do
70
+ error_message = "Method Section::render must be implemented in subclass(es)."
71
+ lambda {subject.send(:render, Object.new, {}) }.should raise_error(NotImplementedError, error_message)
72
+ end
73
+
74
+ end # context
75
+
76
+ end # describe
77
+
78
+
79
+
11
80
 
12
81
  describe ConditionalSection do
13
82
 
14
- context "Creation and initialization" do
83
+ context "Creation and initialization:" do
15
84
 
16
85
  it "should be created with a variable name and a boolean" do
17
86
  lambda { ConditionalSection.new('foobar', false) }.should_not raise_error
18
87
  lambda { ConditionalSection.new('foobar', true) }.should_not raise_error
19
88
  end
20
89
 
21
- it "should know the name of its variable" do
22
- [false, true].each do |existence|
23
- instance = ConditionalSection.new('foobar', existence)
24
- instance.name.should == 'foobar'
25
- end
26
- end
27
-
28
90
  it "should know whether the rendition on existence of actual value or not" do
29
91
  [false, true].each do |existence|
30
92
  instance = ConditionalSection.new('foobar', existence)
@@ -32,16 +94,9 @@ describe ConditionalSection do
32
94
  end
33
95
  end
34
96
 
35
- it "should have no child at start" do
36
- [false, true].each do |existence|
37
- instance = ConditionalSection.new('foobar', existence)
38
- instance.should have(0).children
39
- end
40
- end
41
-
42
97
  end # context
43
98
 
44
- context "Provided services" do
99
+ context "Provided services:" do
45
100
  # Return a list of possible child elements
46
101
  let(:sample_children) do
47
102
  [ StaticText.new("Hello "),
@@ -52,15 +107,9 @@ describe ConditionalSection do
52
107
 
53
108
  # Default instantiation rule
54
109
  subject { ConditionalSection.new('foobar', true) }
55
-
56
- it "should add child element(s)" do
57
- sample_children.each do |a_child|
58
- subject.add_child(a_child)
59
- end
60
-
61
- # Control that the addition worek as expected
62
- subject.children.should == sample_children
63
-
110
+
111
+ it "should know its original source text" do
112
+ subject.to_s.should == '<?foobar>'
64
113
  end
65
114
 
66
115
  it "should render its children when conditions are met" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: macros4cuke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.00
4
+ version: 0.3.01
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-09 00:00:00.000000000 Z
12
+ date: 2013-05-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cucumber
@@ -72,14 +72,20 @@ files:
72
72
  - LICENSE.txt
73
73
  - README.md
74
74
  - lib/macros4cuke.rb
75
- - lib/macro_steps.rb
76
75
  - lib/macros4cuke/constants.rb
77
76
  - lib/macros4cuke/exceptions.rb
78
77
  - lib/macros4cuke/macro-collection.rb
79
78
  - lib/macros4cuke/macro-step-support.rb
80
79
  - lib/macros4cuke/macro-step.rb
81
80
  - lib/macros4cuke/templating/engine.rb
81
+ - examples/demo/cucumber.yml
82
+ - examples/demo/features/basic.feature
83
+ - examples/demo/features/table.feature
82
84
  - examples/i18n/fr/cucumber.yml
85
+ - examples/demo/features/step_definitions/step_defs.rb
86
+ - examples/demo/features/step_definitions/use_macro_steps.rb
87
+ - examples/demo/features/support/env.rb
88
+ - examples/demo/features/support/macro_support.rb
83
89
  - examples/i18n/fr/features/demo01-fr.feature
84
90
  - examples/i18n/fr/features/step_definitions/demo_steps.rb
85
91
  - examples/i18n/fr/features/step_definitions/use_macro_steps.rb
@@ -89,7 +95,7 @@ files:
89
95
  - features/demo03.feature
90
96
  - features/demo04.feature
91
97
  - features/demo05.feature
92
- - features/travelling-demo.feature
98
+ - features/demo06.feature
93
99
  - features/step_definitions/demo_steps.rb
94
100
  - features/step_definitions/use_macro_steps.rb
95
101
  - features/support/env.rb
@@ -99,6 +105,7 @@ files:
99
105
  - spec/macros4cuke/macro-step-support_spec.rb
100
106
  - spec/macros4cuke/macro-step_spec.rb
101
107
  - spec/macros4cuke/templating/engine_spec.rb
108
+ - spec/macros4cuke/templating/placeholder_spec.rb
102
109
  - spec/macros4cuke/templating/section_spec.rb
103
110
  homepage: https://github.com/famished-tiger/Macros4Cuke
104
111
  licenses:
data/lib/macro_steps.rb DELETED
@@ -1,49 +0,0 @@
1
- # File: macro_steps.rb
2
- # Purpose: step definitions that help to build macro-steps (i.e. a step that is equivalent to a sequence of steps)
3
-
4
-
5
-
6
- # This step is used to define a macro-step
7
- # Example:
8
- # Given I define the step "When I [log in as <userid>]" to mean:
9
- # """
10
- # Given I landed in the homepage
11
- # When I click "Sign in"
12
- # And I fill in "Username" with "<userid>"
13
- # And I fill in "Password" with "unguessable"
14
- # And I click "Submit"
15
- # """
16
- # The regexp has two capturing group: one for the phrase, a second for the terminating colon (:)
17
- Given(/^I define the step "(?:Given|When|Then) I \[((?:[^\\\]]|\\.)+)\](:?)" to mean:$/) do |macro_phrase, colon_capture, template|
18
- use_table = (colon_capture == ':')
19
- add_macro(macro_phrase, template, use_table)
20
- end
21
-
22
- # This step is used to invoke a simple macro-step
23
- # Example:
24
- # When I [log in as "guest"]
25
- #
26
- When(/^I \[((?:[^\\\]]|\\.)+)\]$/) do |macro_phrase|
27
- invoke_macro(macro_phrase) # This will call the macro with the given phrase
28
- end
29
-
30
-
31
- # This step is used to invoke a macro-step with a data table argument.
32
- # Example:
33
- # When I [enter my credentials as]:
34
- # |userid |guest |
35
- # |password|unguessable|
36
- When(/^I \[([^\]]+)\]:$/) do |macro_phrase, table_argument|
37
- # Ensure that the second argument is of the correct type
38
- unless table_argument.kind_of?(Cucumber::Ast::Table)
39
- raise Macros4Cuke::DataTableNotFound, "This step must have a data table as an argument."
40
- end
41
-
42
- # This will call the macro with the given phrase.
43
- # The second argument consists of an array with couples of the kind: [argument name, actual value]
44
- invoke_macro(macro_phrase, table_argument.raw)
45
- end
46
-
47
-
48
-
49
- # End of file