macros4cuke 0.3.00 → 0.3.01
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/CHANGELOG.md +12 -0
- data/examples/demo/cucumber.yml +6 -0
- data/examples/demo/features/basic.feature +54 -0
- data/examples/demo/features/step_definitions/step_defs.rb +23 -0
- data/examples/demo/features/step_definitions/use_macro_steps.rb +8 -0
- data/examples/demo/features/support/env.rb +38 -0
- data/examples/demo/features/support/macro_support.rb +16 -0
- data/{features/travelling-demo.feature → examples/demo/features/table.feature} +5 -42
- data/features/demo01.feature +2 -2
- data/features/demo02.feature +2 -2
- data/features/demo03.feature +3 -3
- data/features/demo04.feature +2 -2
- data/features/demo05.feature +2 -2
- data/features/demo06.feature +73 -0
- data/features/step_definitions/demo_steps.rb +0 -19
- data/lib/macros4cuke/constants.rb +1 -1
- data/lib/macros4cuke/macro-step.rb +6 -4
- data/lib/macros4cuke/templating/engine.rb +81 -9
- data/spec/macros4cuke/macro-step_spec.rb +15 -0
- data/spec/macros4cuke/templating/engine_spec.rb +38 -4
- data/spec/macros4cuke/templating/placeholder_spec.rb +65 -0
- data/spec/macros4cuke/templating/section_spec.rb +74 -25
- metadata +11 -4
- data/lib/macro_steps.rb +0 -49
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,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
|
-
|
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
|
-
|
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 "
|
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>|
|
data/features/demo01.feature
CHANGED
@@ -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 "
|
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
|
+
"""
|
data/features/demo02.feature
CHANGED
@@ -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 "
|
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
|
+
"""
|
data/features/demo03.feature
CHANGED
@@ -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 "
|
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 "
|
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
|
+
"""
|
data/features/demo04.feature
CHANGED
@@ -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 "
|
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
|
+
"""
|
data/features/demo05.feature
CHANGED
@@ -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 "
|
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
|
@@ -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
|
-
|
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::#{
|
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.
|
236
|
-
|
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
|
-
|
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
|
-
#
|
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
|
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
|
-
|
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
|
57
|
-
|
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.
|
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-
|
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/
|
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
|