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 +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
|