yard-gherkin-turnip 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +48 -0
- data/History.txt +293 -0
- data/LICENSE.txt +22 -0
- data/README.md +182 -0
- data/Rakefile +24 -0
- data/example/README.md +8 -0
- data/example/child_feature/README.md +21 -0
- data/example/child_feature/child.feature +11 -0
- data/example/child_feature/grandchild_feature/grandchild.feature +12 -0
- data/example/empty.feature +2 -0
- data/example/placeholder.feature +34 -0
- data/example/scenario.feature +63 -0
- data/example/scenario_outline.feature +100 -0
- data/example/scenario_outline_multi.feature +15 -0
- data/example/step_definitions/customer.step.rb +16 -0
- data/example/step_definitions/duck.step.rb +6 -0
- data/example/step_definitions/placeholder.step.rb +70 -0
- data/example/step_definitions/text.step.rb +11 -0
- data/example/tags.feature +18 -0
- data/lib/cucumber/city_builder.rb +412 -0
- data/lib/docserver/default/fulldoc/html/js/cucumber.js +88 -0
- data/lib/docserver/default/layout/html/headers.erb +13 -0
- data/lib/docserver/doc_server/full_list/html/full_list.erb +39 -0
- data/lib/docserver/doc_server/full_list/html/setup.rb +18 -0
- data/lib/templates/default/feature/html/feature.erb +39 -0
- data/lib/templates/default/feature/html/no_steps_defined.erb +1 -0
- data/lib/templates/default/feature/html/outline.erb +59 -0
- data/lib/templates/default/feature/html/pystring.erb +3 -0
- data/lib/templates/default/feature/html/scenario.erb +55 -0
- data/lib/templates/default/feature/html/setup.rb +54 -0
- data/lib/templates/default/feature/html/steps.erb +39 -0
- data/lib/templates/default/feature/html/table.erb +20 -0
- data/lib/templates/default/featuredirectory/html/alpha_table.erb +30 -0
- data/lib/templates/default/featuredirectory/html/directory.erb +32 -0
- data/lib/templates/default/featuredirectory/html/setup.rb +41 -0
- data/lib/templates/default/featuretags/html/namespace.erb +159 -0
- data/lib/templates/default/featuretags/html/setup.rb +34 -0
- data/lib/templates/default/fulldoc/html/css/cucumber.css +226 -0
- data/lib/templates/default/fulldoc/html/directories.erb +27 -0
- data/lib/templates/default/fulldoc/html/full_list_featuredirectories.erb +11 -0
- data/lib/templates/default/fulldoc/html/full_list_features.erb +37 -0
- data/lib/templates/default/fulldoc/html/full_list_stepdefinitions.erb +20 -0
- data/lib/templates/default/fulldoc/html/full_list_steps.erb +20 -0
- data/lib/templates/default/fulldoc/html/full_list_tags.erb +16 -0
- data/lib/templates/default/fulldoc/html/js/cucumber.js +331 -0
- data/lib/templates/default/fulldoc/html/setup.rb +208 -0
- data/lib/templates/default/layout/html/setup.rb +131 -0
- data/lib/templates/default/requirements/html/alpha_table.erb +26 -0
- data/lib/templates/default/requirements/html/requirements.erb +50 -0
- data/lib/templates/default/requirements/html/setup.rb +51 -0
- data/lib/templates/default/steptransformers/html/header.erb +12 -0
- data/lib/templates/default/steptransformers/html/index.erb +10 -0
- data/lib/templates/default/steptransformers/html/placeholders.erb +79 -0
- data/lib/templates/default/steptransformers/html/setup.rb +107 -0
- data/lib/templates/default/steptransformers/html/step_definitions.erb +79 -0
- data/lib/templates/default/steptransformers/html/undefined_steps.erb +26 -0
- data/lib/templates/default/tag/html/alpha_table.erb +33 -0
- data/lib/templates/default/tag/html/setup.rb +27 -0
- data/lib/templates/default/tag/html/tag.erb +35 -0
- data/lib/yard-gherkin-turnip.rb +42 -0
- data/lib/yard-gherkin-turnip/version.rb +3 -0
- data/lib/yard/code_objects/cucumber/base.rb +24 -0
- data/lib/yard/code_objects/cucumber/feature.rb +16 -0
- data/lib/yard/code_objects/cucumber/namespace_object.rb +55 -0
- data/lib/yard/code_objects/cucumber/scenario.rb +22 -0
- data/lib/yard/code_objects/cucumber/scenario_outline.rb +68 -0
- data/lib/yard/code_objects/cucumber/step.rb +46 -0
- data/lib/yard/code_objects/cucumber/tag.rb +31 -0
- data/lib/yard/code_objects/placeholder.rb +45 -0
- data/lib/yard/code_objects/step_definition.rb +46 -0
- data/lib/yard/code_objects/step_transformer.rb +32 -0
- data/lib/yard/handlers/cucumber/base.rb +21 -0
- data/lib/yard/handlers/cucumber/feature_handler.rb +96 -0
- data/lib/yard/handlers/placeholder_handler.rb +28 -0
- data/lib/yard/handlers/placeholder_match_handler.rb +17 -0
- data/lib/yard/handlers/step_definition_handler.rb +55 -0
- data/lib/yard/parser/cucumber/feature.rb +72 -0
- data/lib/yard/server/adapter.rb +43 -0
- data/lib/yard/server/commands/list_command.rb +31 -0
- data/lib/yard/server/router.rb +31 -0
- data/lib/yard/templates/helpers/base_helper.rb +26 -0
- data/yard-gherkin-turnip.gemspec +67 -0
- metadata +216 -0
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
task :default => :gendoc
|
4
|
+
|
5
|
+
desc "Clean out any existing documentation"
|
6
|
+
task :clean do
|
7
|
+
`rm -rf doc`
|
8
|
+
`rm -rf .yardoc`
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Generate documentation from the example data"
|
12
|
+
task :gendoc => :clean do
|
13
|
+
puts `yardoc -e ./lib/yard-gherkin-turnip.rb 'example/**/*' --debug`
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Run the YARD Server"
|
17
|
+
task :server => :gendoc do
|
18
|
+
puts `yard server -e ./lib/yard-gherkin-turnip.rb`
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Create the yard-gherkin-turnip gem"
|
22
|
+
task :gem do
|
23
|
+
puts `gem build yard-gherkin-turnip.gemspec`
|
24
|
+
end
|
data/example/README.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
This collection of features are really just to test some of the varied cases
|
2
|
+
that CITY may come in contact with while parsing a series of features. All
|
3
|
+
of the features, tags, and directories displayed here are contained in this
|
4
|
+
directory and all subdirectories.
|
5
|
+
|
6
|
+
* First, the features which are broken down alphabetically and displayed
|
7
|
+
* Second, the tags, used by all the features and scenarios
|
8
|
+
* Third, the subdirectories, contained in this directory and in the subdirectories.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Child Features
|
2
|
+
==============
|
3
|
+
|
4
|
+
Synopsis
|
5
|
+
--------
|
6
|
+
|
7
|
+
This collection of features are contained in this folder. The description README
|
8
|
+
allows a user to place a description for entire directory of the features and appears
|
9
|
+
in the output to assist with understanding about the collection of features.
|
10
|
+
|
11
|
+
Resources
|
12
|
+
---------
|
13
|
+
|
14
|
+
Links to particular resources like the links to the stories, tasks, or other areas
|
15
|
+
can also be represented.
|
16
|
+
|
17
|
+
The implemented example has been deployed at [http://recursivegames.com/cukes/](http://recursivegames.com/cukes/).
|
18
|
+
|
19
|
+
**1. An Item** [example](http://recursivegames.com/cukes/requirements/)
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
@scenarios
|
2
|
+
Feature: Child Feature
|
3
|
+
As a reader of the documentation I expect that scenario are documented correctly
|
4
|
+
|
5
|
+
Background:
|
6
|
+
Given this background step
|
7
|
+
|
8
|
+
Scenario: Child Scenario
|
9
|
+
Given this first step
|
10
|
+
When this second step
|
11
|
+
Then this third step
|
@@ -0,0 +1,12 @@
|
|
1
|
+
@scenarios
|
2
|
+
Feature: Grandchild Feature
|
3
|
+
As a reader of the documentation I expect that scenario are documented correctly
|
4
|
+
|
5
|
+
Background:
|
6
|
+
Given this background step
|
7
|
+
|
8
|
+
@first
|
9
|
+
Scenario: Grandchild Scenario
|
10
|
+
Given this first step
|
11
|
+
When this second step
|
12
|
+
Then this third step
|
@@ -0,0 +1,34 @@
|
|
1
|
+
@scenarios @bvt
|
2
|
+
Feature: Step Placeholders
|
3
|
+
As a developer of the test suite I expect that step placeholders are documented correctly
|
4
|
+
|
5
|
+
@first
|
6
|
+
Scenario: Step with custom placeholders
|
7
|
+
Given this scenario step
|
8
|
+
Then I expect that the step, on the step transformer page, will link to the step transform
|
9
|
+
|
10
|
+
@first
|
11
|
+
Scenario: Step with custom placeholders
|
12
|
+
Given the first step
|
13
|
+
Then I expect that the step, on the step transformer page, will link to the step transform
|
14
|
+
|
15
|
+
@second
|
16
|
+
Scenario: Step with simple placeholders
|
17
|
+
Given the step called 'bob'
|
18
|
+
And the steps called 'pop'
|
19
|
+
Then I expect that the step, on the step transformer page, will link to the step transform
|
20
|
+
|
21
|
+
@third
|
22
|
+
Scenario: Step with multiple custom placeholders
|
23
|
+
Given the second scenario step on 12/04/2020
|
24
|
+
Then I expect that the step, on the step transformer page, will link to the step transform
|
25
|
+
|
26
|
+
@third
|
27
|
+
Scenario: Step with both simple and custom placeholders
|
28
|
+
Given the second 'bob' step
|
29
|
+
Then I expect that the step, on the step transformer page, will link to the step transform
|
30
|
+
|
31
|
+
Scenario: Step with complex placeholder reges
|
32
|
+
Given the step at the [0 + 1] index
|
33
|
+
Then I expect that the step, on the step transformer page, will link to the step transform
|
34
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Comments that appear before the feature are associated with the feature
|
2
|
+
@scenarios
|
3
|
+
Feature: Displaying Scenarios
|
4
|
+
As a reader of the documentation I expect that scenario are documented correctly
|
5
|
+
|
6
|
+
# Comments after the feature description belong to the background or first scenario
|
7
|
+
Background:
|
8
|
+
Given this background step
|
9
|
+
|
10
|
+
@first @bvt
|
11
|
+
Scenario: No Step Scenario
|
12
|
+
|
13
|
+
@second @bvt
|
14
|
+
Scenario: Scenario With Steps
|
15
|
+
Given this first step
|
16
|
+
When this second step
|
17
|
+
Then this third step
|
18
|
+
|
19
|
+
@third @optional_parameters
|
20
|
+
Scenario: Optional Parameter Step Definition
|
21
|
+
# This step definition has some optional parameters
|
22
|
+
Given a project
|
23
|
+
And an inactive project
|
24
|
+
And a project with the name 'optional', start date 10/26/2010, nicknamed 'norman'
|
25
|
+
|
26
|
+
@fourth @highlight
|
27
|
+
Scenario: Matched Term Highlighting
|
28
|
+
Given a duck that has a bill
|
29
|
+
Then I expect the duck to quack
|
30
|
+
|
31
|
+
@fifth @table
|
32
|
+
Scenario: Scenario With Table
|
33
|
+
Given the following table:
|
34
|
+
| column 1 | column 2 | column 3 |
|
35
|
+
| value 1 | value 2 | value 3 |
|
36
|
+
|
37
|
+
@sixth @text
|
38
|
+
Scenario: Scenario With Text
|
39
|
+
Given the following text:
|
40
|
+
"""
|
41
|
+
Oh what a bother!
|
42
|
+
That this text has to take up two lines
|
43
|
+
This line should be indented 2 spaces
|
44
|
+
This line should be indented 4 spaces
|
45
|
+
"""
|
46
|
+
|
47
|
+
# Comments before the scenario
|
48
|
+
@seventh @comments
|
49
|
+
Scenario: Scenario with comments and a description
|
50
|
+
There once was a need for information to be displayed alongside all the
|
51
|
+
entities that I hoped to test
|
52
|
+
# First Comment
|
53
|
+
Given this first step
|
54
|
+
# Second Comment that
|
55
|
+
# spans a few lines
|
56
|
+
And this second step
|
57
|
+
# Third Comment
|
58
|
+
And this third step
|
59
|
+
# Comments after the last step, where do they go?
|
60
|
+
|
61
|
+
Scenario: Step ending with a match with double-quotes
|
62
|
+
When searching the log for the exact match of the message "Entering application."
|
63
|
+
When the step definition has HTML escaped characters like: "<>&"
|
@@ -0,0 +1,100 @@
|
|
1
|
+
@scenario_outlines @bvt @duplicate
|
2
|
+
Feature: Displaying Scenario Outlines
|
3
|
+
As a reader of the documentation I expect that scenario outlines are documented correctly
|
4
|
+
|
5
|
+
@first
|
6
|
+
Scenario Outline: Three Examples
|
7
|
+
Given that <Customer> is a valid customer
|
8
|
+
And that the product, named '<Product>', is a valid product
|
9
|
+
When the customer has purchased the product
|
10
|
+
Then I expect the customer to be a member of the '<Product>' group
|
11
|
+
|
12
|
+
@tagged_examples
|
13
|
+
Examples:
|
14
|
+
| Customer | Product |
|
15
|
+
| Customer A | Product A |
|
16
|
+
| Customer A | Product B |
|
17
|
+
| Customer A | Product C |
|
18
|
+
|
19
|
+
@second
|
20
|
+
Scenario Outline: Step contains a text block
|
21
|
+
Given the following text:
|
22
|
+
"""
|
23
|
+
The <noun> jumped over the <place>
|
24
|
+
"""
|
25
|
+
When I click on an example row
|
26
|
+
Then I expect <noun> to be replaced by the example noun
|
27
|
+
And I expect <place> to be replaced by the example place
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
| noun | place |
|
31
|
+
| cow | moon |
|
32
|
+
| horse | spoon |
|
33
|
+
|
34
|
+
@third
|
35
|
+
Scenario Outline: Step contains a table
|
36
|
+
Given the following table:
|
37
|
+
| name | price | quantity |
|
38
|
+
| <name> | <price> | 100000 |
|
39
|
+
When I click on an example row
|
40
|
+
Then I expect <name> to be replaced by the example name
|
41
|
+
And I expect <price> to be replaced by the example price
|
42
|
+
|
43
|
+
Examples:
|
44
|
+
| name | price |
|
45
|
+
| toy | $99 |
|
46
|
+
| game | $49 |
|
47
|
+
|
48
|
+
@fourth
|
49
|
+
Scenario Outline: Step contains a table; table header uses an example
|
50
|
+
Given the following table:
|
51
|
+
| name | <denomination> | quantity |
|
52
|
+
| <name> | <price> | 100000 |
|
53
|
+
When I click on an example row
|
54
|
+
Then I expect <name> to be replaced by the example name
|
55
|
+
And I expect <price> to be replaced by the example price
|
56
|
+
And I expect <denomination> to be replaced by the example denomination
|
57
|
+
|
58
|
+
Examples:
|
59
|
+
| name | price | denomination |
|
60
|
+
| toy | 99 | cost in euros |
|
61
|
+
| game | 49 | cost in dollars |
|
62
|
+
|
63
|
+
# This is an example of a scenario outline in development.
|
64
|
+
# The example table has not been defined yet
|
65
|
+
@fifth
|
66
|
+
Scenario Outline: Example Table Missing
|
67
|
+
When I click on an example row
|
68
|
+
Then I expect <name> to be replaced by the example name
|
69
|
+
And I expect <price> to be replaced by the example price
|
70
|
+
And I expect <denomination> to be replaced by the example denomination
|
71
|
+
|
72
|
+
|
73
|
+
# This is an example of a scenario outline in development.
|
74
|
+
# The examples table has been defined, but is missing data.
|
75
|
+
@sixth
|
76
|
+
Scenario Outline: Empty Example Table
|
77
|
+
When I click on an example row
|
78
|
+
Then I expect <name> to be replaced by the example name
|
79
|
+
And I expect <price> to be replaced by the example price
|
80
|
+
And I expect <denomination> to be replaced by the example denomination
|
81
|
+
|
82
|
+
Examples:
|
83
|
+
| name | price | denomination |
|
84
|
+
|
85
|
+
@seventh @duplicate
|
86
|
+
Scenario Outline: Multiple Example Table
|
87
|
+
Given that <Customer> is a valid customer
|
88
|
+
And that the product, named '<Product>', is a valid product
|
89
|
+
When the customer has purchased the product
|
90
|
+
Then I expect the customer to be a member of the '<Product>' group
|
91
|
+
|
92
|
+
@groupA @duplicate
|
93
|
+
Examples: Example group A
|
94
|
+
| Customer | Product |
|
95
|
+
| Customer A | Product A |
|
96
|
+
|
97
|
+
@groupB
|
98
|
+
Examples: Example group B
|
99
|
+
| Customer | Product |
|
100
|
+
| Customer B | Product A |
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Feature: My Feature
|
2
|
+
|
3
|
+
Scenario Outline: Multiple Example Table
|
4
|
+
Given that <Customer> is a valid customer
|
5
|
+
And that the product, named '<Product>', is a valid product
|
6
|
+
When the customer has purchased the product
|
7
|
+
Then I expect the customer to be a member of the '<Product>' group
|
8
|
+
|
9
|
+
Examples: Example group A
|
10
|
+
| Customer | Product |
|
11
|
+
| Customer A | Product A |
|
12
|
+
|
13
|
+
Examples: Example group B
|
14
|
+
| Customer | Product |
|
15
|
+
| Customer B | Product A |
|
@@ -0,0 +1,16 @@
|
|
1
|
+
step "that Customer :customer_number is a valid customer" do
|
2
|
+
end
|
3
|
+
|
4
|
+
step "that the product, named ':product', is a valid product" do
|
5
|
+
end
|
6
|
+
|
7
|
+
step "the customer has purchased the product" do
|
8
|
+
skip # pending step
|
9
|
+
end
|
10
|
+
|
11
|
+
step "I expect the customer to be a member of the ':product_type' group" do
|
12
|
+
end
|
13
|
+
|
14
|
+
placeholder :product_type do
|
15
|
+
match %r{(Product A|Product B|Product C)}
|
16
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# This step definition uses a placeholder custom
|
3
|
+
#
|
4
|
+
step "the/this :step_type step" do |step_type|
|
5
|
+
puts "step type #{step_type}"
|
6
|
+
end
|
7
|
+
|
8
|
+
#
|
9
|
+
# This step definition uses a custom placeholder with forward slashes
|
10
|
+
#
|
11
|
+
step "the :step_number :step_type step on :date" do |step_number, step_type, day, month, year|
|
12
|
+
puts "step type #{step_type}"
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# This step definition uses alternative text syntax
|
17
|
+
#
|
18
|
+
step "the/this :step_number step" do |step_type|
|
19
|
+
puts "step number #{step_type}"
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# This step definition uses a default placeholder with optional text
|
24
|
+
#
|
25
|
+
step "the step(s) called :name" do |name|
|
26
|
+
puts "name #{name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
#
|
31
|
+
# This step definition uses both default and custom placeholders
|
32
|
+
#
|
33
|
+
step "the :step_number :name step" do |step_number, name|
|
34
|
+
puts "step_number #{step_number} and name #{name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# This step definition uses a custom placeholder with regex special chars
|
39
|
+
#
|
40
|
+
step "the step at the [:first_term :arithmetic_operation :first_term] index" do
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Shouldn't match our handler as it's not inside a placeholder
|
45
|
+
#
|
46
|
+
match /blob/ do
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# This placeholder matches the type of step
|
51
|
+
#
|
52
|
+
placeholder :step_type do
|
53
|
+
match /(?:background|scenario)/
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# This placeholder matches the step number using 2 separate calls to match
|
58
|
+
#
|
59
|
+
placeholder :step_number do
|
60
|
+
match(/(?:first|second)/)
|
61
|
+
match(/third/)
|
62
|
+
end
|
63
|
+
|
64
|
+
placeholder :date do
|
65
|
+
match /(\d\d)\/(\d\d)\/(\d\d\d\d)/
|
66
|
+
end
|
67
|
+
|
68
|
+
placeholder :arithmetic_operation do
|
69
|
+
match /\+|-|\*|\//
|
70
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
@tags
|
2
|
+
Feature: Tags
|
3
|
+
As a developer of the test suite I expect that various tags will be supported
|
4
|
+
|
5
|
+
@tag
|
6
|
+
Scenario: Basic Tag
|
7
|
+
|
8
|
+
@tag123456
|
9
|
+
Scenario: Tag With Numbers
|
10
|
+
|
11
|
+
@tag_with_underscore
|
12
|
+
Scenario: Tag With Underscore
|
13
|
+
|
14
|
+
@tag-with-dash
|
15
|
+
Scenario: Tag With Dash
|
16
|
+
|
17
|
+
@tag+with+plus
|
18
|
+
Scenario: Tag With Plus
|
@@ -0,0 +1,412 @@
|
|
1
|
+
module Cucumber
|
2
|
+
module Parser
|
3
|
+
class CityBuilder < ::Gherkin::AstBuilder
|
4
|
+
|
5
|
+
#
|
6
|
+
# The Gherkin Parser is going to call the various methods within this
|
7
|
+
# class as it finds items. This is similar to how Cucumber generates
|
8
|
+
# it's Abstract Syntax Tree (AST). Here instead this generates the
|
9
|
+
# various YARD::CodeObjects defined within this template.
|
10
|
+
#
|
11
|
+
# A namespace is specified and that is the place in the YARD namespacing
|
12
|
+
# where all cucumber features generated will reside. The namespace specified
|
13
|
+
# is the root namespaces.
|
14
|
+
#
|
15
|
+
# @param [String] file the name of the file which the content belongs
|
16
|
+
#
|
17
|
+
def initialize(file)
|
18
|
+
super()
|
19
|
+
@namespace = YARD::CodeObjects::Cucumber::CUCUMBER_NAMESPACE
|
20
|
+
find_or_create_namespace(file)
|
21
|
+
@file = file
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return the feature that has been defined. This method is the final
|
25
|
+
# method that is called when all the work is done. It is called by
|
26
|
+
# the feature parser to return the complete Feature object that was created
|
27
|
+
#
|
28
|
+
# @return [YARD::CodeObject::Cucumber::Feature] the completed feature
|
29
|
+
#
|
30
|
+
# @see YARD::Parser::Cucumber::FeatureParser
|
31
|
+
def ast
|
32
|
+
feature(get_result) unless @feature
|
33
|
+
@feature
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Feature that are found in sub-directories are considered, in the way
|
38
|
+
# that I chose to implement it, in another namespace. This is because
|
39
|
+
# when you execute a cucumber test run on a directory any sub-directories
|
40
|
+
# of features will be executed with that directory so the file is split
|
41
|
+
# and then namespaces are generated if they have not already have been.
|
42
|
+
#
|
43
|
+
# The other duty that this does is look for a README.md file within the
|
44
|
+
# specified directory of the file and loads it as the description for the
|
45
|
+
# namespace. This is useful if you want to give a particular directory
|
46
|
+
# some flavor or text to describe what is going on.
|
47
|
+
#
|
48
|
+
def find_or_create_namespace(file)
|
49
|
+
@namespace = YARD::CodeObjects::Cucumber::CUCUMBER_NAMESPACE
|
50
|
+
|
51
|
+
File.dirname(file).split('/').each do |directory|
|
52
|
+
@namespace = @namespace.children.find {|child| child.is_a?(YARD::CodeObjects::Cucumber::FeatureDirectory) && child.name.to_s == directory } ||
|
53
|
+
@namespace = YARD::CodeObjects::Cucumber::FeatureDirectory.new(@namespace,directory) {|dir| dir.add_file(directory)}
|
54
|
+
end
|
55
|
+
|
56
|
+
if @namespace.description == "" && File.exists?("#{File.dirname(file)}/README.md")
|
57
|
+
@namespace.description = File.read("#{File.dirname(file)}/README.md")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Find the tag if it exists within the YARD Registry, if it doesn't then
|
63
|
+
# create it.
|
64
|
+
#
|
65
|
+
# We note that the tag was used in this file at the current line.
|
66
|
+
#
|
67
|
+
# Then we add the tag to the current scenario or feature. We also add the
|
68
|
+
# feature or scenario to the tag.
|
69
|
+
#
|
70
|
+
# @param [String] tag_name the name of the tag
|
71
|
+
# @param [parent] parent the scenario or feature that is going to adopt
|
72
|
+
# this tag.
|
73
|
+
#
|
74
|
+
def find_or_create_tag(tag_name,parent)
|
75
|
+
#log.debug "Processing tag #{tag_name}"
|
76
|
+
tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.value == tag_name } ||
|
77
|
+
YARD::CodeObjects::Cucumber::Tag.new(YARD::CodeObjects::Cucumber::CUCUMBER_TAG_NAMESPACE,tag_name.gsub('@','')) {|t| t.owners = [] ; t.value = tag_name ; t.total_scenarios = 0}
|
78
|
+
|
79
|
+
tag_code_object.add_file(@file,parent.line)
|
80
|
+
|
81
|
+
parent.tags << tag_code_object unless parent.tags.find {|tag| tag == tag_code_object }
|
82
|
+
tag_code_object.owners << parent unless tag_code_object.owners.find {|owner| owner == parent}
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Each feature found will call this method, generating the feature object.
|
87
|
+
# This is once, as the gherkin parser does not like multiple feature per
|
88
|
+
# file.
|
89
|
+
#
|
90
|
+
def feature(document)
|
91
|
+
#log.debug "FEATURE"
|
92
|
+
feature = document[:feature]
|
93
|
+
return unless document[:feature]
|
94
|
+
return if has_exclude_tags?(feature[:tags].map { |t| t[:name].gsub(/^@/, '') })
|
95
|
+
|
96
|
+
@feature = YARD::CodeObjects::Cucumber::Feature.new(@namespace,File.basename(@file.gsub('.feature','').gsub('.','_'))) do |f|
|
97
|
+
f.comments = feature[:comments] ? feature[:comments].map{|comment| comment[:text]}.join("\n") : ''
|
98
|
+
f.description = feature[:description] || ''
|
99
|
+
f.add_file(@file,feature[:location][:line])
|
100
|
+
f.keyword = feature[:keyword]
|
101
|
+
f.value = feature[:name]
|
102
|
+
f.tags = []
|
103
|
+
|
104
|
+
feature[:tags].each {|feature_tag| find_or_create_tag(feature_tag[:name],f) }
|
105
|
+
end
|
106
|
+
|
107
|
+
background(feature[:background]) if feature[:background]
|
108
|
+
|
109
|
+
feature[:children].each do |child|
|
110
|
+
case child[:type]
|
111
|
+
when :Background
|
112
|
+
background(child)
|
113
|
+
when :ScenarioOutline
|
114
|
+
outline = scenario_outline(child)
|
115
|
+
@feature.total_scenarios += outline.scenarios.size
|
116
|
+
when :Scenario
|
117
|
+
scenario(child)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
@feature.tags.each do |feature_tag|
|
122
|
+
tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.name.to_s == feature_tag[:name].to_s }
|
123
|
+
tag_code_object.total_scenarios += @feature.total_scenarios
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Called when a background has been found
|
129
|
+
#
|
130
|
+
# @see #feature
|
131
|
+
def background(background)
|
132
|
+
#log.debug "BACKGROUND"
|
133
|
+
|
134
|
+
@background = YARD::CodeObjects::Cucumber::Scenario.new(@feature,"background") do |b|
|
135
|
+
b.comments = background[:comments] ? background[:comments].map{|comment| comment.value}.join("\n") : ''
|
136
|
+
b.description = background[:description] || ''
|
137
|
+
b.keyword = background[:keyword]
|
138
|
+
b.value = background[:name]
|
139
|
+
b.add_file(@file,background[:location][:line])
|
140
|
+
end
|
141
|
+
|
142
|
+
@feature.background = @background
|
143
|
+
@background.feature = @feature
|
144
|
+
@step_container = @background
|
145
|
+
background[:steps].each { |s|
|
146
|
+
step(s)
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Called when a scenario has been found
|
152
|
+
# - create a scenario
|
153
|
+
# - assign the scenario to the feature
|
154
|
+
# - assign the feature to the scenario
|
155
|
+
# - find or create tags associated with the scenario
|
156
|
+
#
|
157
|
+
# The scenario is set as the @step_container, which means that any steps
|
158
|
+
# found before another scenario is defined belong to this scenario
|
159
|
+
#
|
160
|
+
# @param [Scenario] statement is a scenario object returned from Gherkin
|
161
|
+
# @see #find_or_create_tag
|
162
|
+
#
|
163
|
+
def scenario(statement)
|
164
|
+
#log.debug "SCENARIO"
|
165
|
+
|
166
|
+
return if has_exclude_tags?(statement[:tags].map { |t| t[:name].gsub(/^@/, '') })
|
167
|
+
|
168
|
+
scenario = YARD::CodeObjects::Cucumber::Scenario.new(@feature,"scenario_#{@feature.scenarios.length + 1}") do |s|
|
169
|
+
s.comments = statement[:comments] ? statement[:comments].map{|comment| comment.value}.join("\n") : ''
|
170
|
+
s.description = statement[:description] || ''
|
171
|
+
s.add_file(@file,statement[:location][:line])
|
172
|
+
s.keyword = statement[:keyword]
|
173
|
+
s.value = statement[:name]
|
174
|
+
|
175
|
+
statement[:tags].each {|scenario_tag| find_or_create_tag(scenario_tag[:name],s) }
|
176
|
+
end
|
177
|
+
|
178
|
+
scenario.feature = @feature
|
179
|
+
@feature.scenarios << scenario
|
180
|
+
@step_container = scenario
|
181
|
+
statement[:steps].each { |s|
|
182
|
+
step(s)
|
183
|
+
}
|
184
|
+
|
185
|
+
# count scenarios for scenario level tags
|
186
|
+
scenario.tags.uniq.each { |scenario_tag|
|
187
|
+
if !scenario.feature.tags.include?(scenario_tag)
|
188
|
+
tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.name.to_s == scenario_tag[:name].to_s }
|
189
|
+
tag_code_object.total_scenarios += 1
|
190
|
+
end
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
# Called when a scenario outline is found. Very similar to a scenario,
|
196
|
+
# the ScenarioOutline is still a distinct object as it can contain
|
197
|
+
# multiple different example groups that can contain different values.
|
198
|
+
#
|
199
|
+
# @see #scenario
|
200
|
+
#
|
201
|
+
def scenario_outline(statement)
|
202
|
+
#log.debug "SCENARIO OUTLINE"
|
203
|
+
|
204
|
+
return if has_exclude_tags?(statement[:tags].map { |t| t[:name].gsub(/^@/, '') })
|
205
|
+
|
206
|
+
outline = YARD::CodeObjects::Cucumber::ScenarioOutline.new(@feature,"scenario_#{@feature.scenarios.length + 1}") do |s|
|
207
|
+
s.comments = statement[:comments] ? statement[:comments].map{|comment| comment.value}.join("\n") : ''
|
208
|
+
s.description = statement[:description] || ''
|
209
|
+
s.add_file(@file,statement[:location][:line])
|
210
|
+
s.keyword = statement[:keyword]
|
211
|
+
s.value = statement[:name]
|
212
|
+
|
213
|
+
statement[:tags].each {|scenario_tag| find_or_create_tag(scenario_tag[:name],s) }
|
214
|
+
end
|
215
|
+
|
216
|
+
outline.feature = @feature
|
217
|
+
@step_container = outline
|
218
|
+
statement[:steps].each { |s|
|
219
|
+
step(s)
|
220
|
+
}
|
221
|
+
|
222
|
+
statement[:examples].each { |e|
|
223
|
+
example = examples(e, outline)
|
224
|
+
}
|
225
|
+
|
226
|
+
@feature.scenarios << outline
|
227
|
+
|
228
|
+
# count scenarios for scenario outline level tags
|
229
|
+
outline.tags.uniq.each { |outline_tag|
|
230
|
+
if !outline.feature.tags.include?(outline_tag)
|
231
|
+
tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.name.to_s == outline_tag[:name].to_s }
|
232
|
+
tag_code_object.total_scenarios += outline.scenarios.size
|
233
|
+
end
|
234
|
+
}
|
235
|
+
|
236
|
+
# count scenarios for example table level tags
|
237
|
+
outline.examples.each { |example|
|
238
|
+
unless !example.tags.any?
|
239
|
+
example.tags.uniq.each { |example_tag|
|
240
|
+
if !outline.feature.tags.include?(example_tag) && !outline.tags.include?(example_tag)
|
241
|
+
tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.name.to_s == example_tag[:name].to_s }
|
242
|
+
tag_code_object.total_scenarios += example.data.size
|
243
|
+
end
|
244
|
+
}
|
245
|
+
end
|
246
|
+
}
|
247
|
+
|
248
|
+
return outline
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
#
|
253
|
+
# Examples for a scenario outline are called here. This section differs
|
254
|
+
# from the Cucumber parser because here each of the examples are exploded
|
255
|
+
# out here as individual scenarios and step definitions. This is so that
|
256
|
+
# later we can ensure that we have all the variations of the scenario
|
257
|
+
# outline defined to be displayed.
|
258
|
+
#
|
259
|
+
def examples(examples, outline)
|
260
|
+
#log.debug "EXAMPLES"
|
261
|
+
return if has_exclude_tags?(examples[:tags].map { |t| t[:name].gsub(/^@/, '') })
|
262
|
+
example = YARD::CodeObjects::Cucumber::ScenarioOutline::Examples.new(:keyword => examples[:keyword],
|
263
|
+
:name => examples[:name],
|
264
|
+
:line => examples[:location][:line],
|
265
|
+
:comments => examples[:comments] ? examples.comments.map{|comment| comment.value}.join("\n") : '',
|
266
|
+
:rows => [],
|
267
|
+
:tags => [],
|
268
|
+
:scenario => outline )
|
269
|
+
|
270
|
+
unless !examples[:tags].any?
|
271
|
+
examples[:tags].each {|example_tag| find_or_create_tag(example_tag[:name], example)}
|
272
|
+
end
|
273
|
+
|
274
|
+
example.rows = [examples[:tableHeader][:cells].map{ |c| c[:value] }] if examples[:tableHeader]
|
275
|
+
example.rows += matrix(examples[:tableBody]) if examples[:tableBody]
|
276
|
+
|
277
|
+
# add the example to the step containers list of examples
|
278
|
+
|
279
|
+
@step_container.examples << example
|
280
|
+
|
281
|
+
# For each example data row we want to generate a new scenario using our
|
282
|
+
# current scenario as the template.
|
283
|
+
|
284
|
+
example.data.length.times do |row_index|
|
285
|
+
|
286
|
+
# Generate a copy of the scenario.
|
287
|
+
|
288
|
+
scenario = YARD::CodeObjects::Cucumber::Scenario.new(@step_container,"example_#{@step_container.scenarios.length + 1}") do |s|
|
289
|
+
s.comments = @step_container.comments
|
290
|
+
s.description = @step_container.description
|
291
|
+
s.add_file(@file,@step_container.line_number)
|
292
|
+
s.keyword = @step_container.keyword
|
293
|
+
s.value = "#{@step_container.value} (#{@step_container.scenarios.length + 1})"
|
294
|
+
end
|
295
|
+
|
296
|
+
# Generate a copy of the scenario steps.
|
297
|
+
|
298
|
+
@step_container.steps.each do |step|
|
299
|
+
step_instance = YARD::CodeObjects::Cucumber::Step.new(scenario,step.line_number.to_s) do |s|
|
300
|
+
s.keyword = step.keyword.dup
|
301
|
+
s.value = step.value.dup
|
302
|
+
s.add_file(@file,step.line_number)
|
303
|
+
|
304
|
+
s.text = step.text.dup if step.has_text?
|
305
|
+
s.table = clone_table(step.table) if step.has_table?
|
306
|
+
end
|
307
|
+
|
308
|
+
# Look at the particular data for the example row and do a simple
|
309
|
+
# find and replace of the <key> with the associated values.
|
310
|
+
|
311
|
+
example.values_for_row(row_index).each do |key,text|
|
312
|
+
text ||= "" #handle empty cells in the example table
|
313
|
+
step_instance.value.gsub!("<#{key}>",text)
|
314
|
+
step_instance.text.gsub!("<#{key}>",text) if step_instance.has_text?
|
315
|
+
step_instance.table.each{|row| row.each{|col| col.gsub!("<#{key}>",text)}} if step_instance.has_table?
|
316
|
+
end
|
317
|
+
|
318
|
+
# Connect these steps that we created to the scenario we created
|
319
|
+
# and then add the steps to the scenario created.
|
320
|
+
|
321
|
+
step_instance.scenario = scenario
|
322
|
+
scenario.steps << step_instance
|
323
|
+
end
|
324
|
+
|
325
|
+
# Add the scenario to the list of scenarios maintained by the feature
|
326
|
+
# and add the feature to the scenario
|
327
|
+
|
328
|
+
scenario.feature = @feature
|
329
|
+
@step_container.scenarios << scenario
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
return example
|
334
|
+
end
|
335
|
+
|
336
|
+
#
|
337
|
+
# Called when a step is found. The step is refered to a table owner, though
|
338
|
+
# not all steps have a table or multliline arguments associated with them.
|
339
|
+
#
|
340
|
+
# If a multiline string is present with the step it is included as the text
|
341
|
+
# of the step. If the step has a table it is added to the step using the
|
342
|
+
# same method used by the Cucumber Gherkin model.
|
343
|
+
#
|
344
|
+
def step(step)
|
345
|
+
#log.debug "STEP"
|
346
|
+
|
347
|
+
@table_owner = YARD::CodeObjects::Cucumber::Step.new(@step_container,"#{step[:location][:line]}") do |s|
|
348
|
+
s.keyword = step[:keyword]
|
349
|
+
s.value = step[:text]
|
350
|
+
s.add_file(@file,step[:location][:line])
|
351
|
+
end
|
352
|
+
|
353
|
+
@table_owner.comments = step[:comments] ? step[:comments].map{|comment| comment.value}.join("\n") : ''
|
354
|
+
|
355
|
+
multiline_arg = step[:argument]
|
356
|
+
|
357
|
+
case(multiline_arg[:type])
|
358
|
+
when :DocString
|
359
|
+
@table_owner.text = multiline_arg[:content]
|
360
|
+
when :DataTable
|
361
|
+
#log.info "Matrix: #{matrix(multiline_arg).collect{|row| row.collect{|cell| cell.class } }.flatten.join("\n")}"
|
362
|
+
@table_owner.table = matrix(multiline_arg[:rows])
|
363
|
+
end if multiline_arg
|
364
|
+
|
365
|
+
@table_owner.scenario = @step_container
|
366
|
+
@step_container.steps << @table_owner
|
367
|
+
end
|
368
|
+
|
369
|
+
# Defined in the cucumber version so left here. No events for the end-of-file
|
370
|
+
def eof
|
371
|
+
end
|
372
|
+
|
373
|
+
# When a syntax error were to occurr. This parser is not interested in errors
|
374
|
+
def syntax_error(state, event, legal_events, line)
|
375
|
+
# raise "SYNTAX ERROR"
|
376
|
+
end
|
377
|
+
|
378
|
+
private
|
379
|
+
def matrix(gherkin_table)
|
380
|
+
gherkin_table.map {|gherkin_row| gherkin_row[:cells].map{ |cell| cell[:value] } }
|
381
|
+
end
|
382
|
+
|
383
|
+
#
|
384
|
+
# This helper method is used to deteremine what class is the current
|
385
|
+
# Gherkin class.
|
386
|
+
#
|
387
|
+
# @return [Class] the class that is the current supported Gherkin Model
|
388
|
+
# for multiline strings. Prior to Gherkin 2.4.0 this was the PyString
|
389
|
+
# class. As of Gherkin 2.4.0 it is the DocString class.
|
390
|
+
def gherkin_multiline_string_class
|
391
|
+
if defined?(Gherkin::Formatter::Model::PyString)
|
392
|
+
Gherkin::Formatter::Model::PyString
|
393
|
+
elsif defined?(Gherkin::Formatter::Model::DocString)
|
394
|
+
Gherkin::Formatter::Model::DocString
|
395
|
+
else
|
396
|
+
raise "Unable to find a suitable class in the Gherkin Library to parse the multiline step data."
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def clone_table(base)
|
401
|
+
base.map {|row| row.map {|cell| cell.dup }}
|
402
|
+
end
|
403
|
+
|
404
|
+
def has_exclude_tags?(tags)
|
405
|
+
if YARD::Config.options["yard-gherkin-turnip"] and YARD::Config.options["yard-gherkin-turnip"]["exclude_tags"]
|
406
|
+
return true unless (YARD::Config.options["yard-gherkin-turnip"]["exclude_tags"] & tags).empty?
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|