Skip to content

Commit

Permalink
Support Rule keyword
Browse files Browse the repository at this point in the history
Note that rules are not indented as that would complicate the logic considerably: every print directive would need to check if it is surrounded by a rule or not.
  • Loading branch information
michaelsauter committed Dec 1, 2021
1 parent df29469 commit e19f7af
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 12 deletions.
175 changes: 175 additions & 0 deletions features/formatter/pretty.feature
Expand Up @@ -23,6 +23,181 @@ Feature: pretty formatter
0s
"""

Scenario: Support of Feature Plus Background and Scenario Node
Given a feature "features/simple.feature" file:
"""
Feature: simple feature
simple feature description
Background: simple background
Given passing step
And passing step
Scenario: simple scenario
simple scenario description
"""
When I run feature suite with formatter "pretty"
Then the rendered output will be as follows:
"""
Feature: simple feature
simple feature description
Background: simple background
Given passing step
And passing step
Scenario: simple scenario # features/simple.feature:6
1 scenarios (1 undefined)
No steps
0s
"""

Scenario: Support of Feature Plus Rule Node
Given a feature "features/simple.feature" file:
"""
Feature: simple feature
simple feature description
Rule: simple rule
Scenario: simple scenario
simple scenario description
"""
When I run feature suite with formatter "pretty"
Then the rendered output will be as follows:
"""
Feature: simple feature
simple feature description
Rule: simple rule
Scenario: simple scenario # features/simple.feature:4
1 scenarios (1 undefined)
No steps
0s
"""

Scenario: Support of Feature Plus Rule Node with Background
Given a feature "features/simple.feature" file:
"""
Feature: simple feature
simple feature description
Rule: simple rule
Background: simple background
Given something
And another thing
Scenario: simple scenario
simple scenario description
"""
When I run feature suite with formatter "pretty"
Then the rendered output will be as follows:
"""
Feature: simple feature
simple feature description
Rule: simple rule
Background: simple background
Given something
And another thing
Scenario: simple scenario # features/simple.feature:10
1 scenarios (1 undefined)
No steps
0s
"""

Scenario: Support of Feature Plus Rule Node with multiple scenarios
Given a feature "features/simple.feature" file:
"""
Feature: simple feature
simple feature description
Rule: simple rule
Scenario: simple scenario
simple scenario description
Given passing step
Then passing step
Scenario: simple second scenario
simple second scenario description
Given passing step
Then passing step
"""
When I run feature suite with formatter "pretty"
Then the rendered output will be as follows:
"""
Feature: simple feature
simple feature description
Rule: simple rule
Scenario: simple scenario # features/simple.feature:6
Given passing step # suite_context_test.go:0 -> InitializeScenario.func2
Then passing step # suite_context_test.go:0 -> InitializeScenario.func2
Scenario: simple second scenario # features/simple.feature:12
Given passing step # suite_context_test.go:0 -> InitializeScenario.func2
Then passing step # suite_context_test.go:0 -> InitializeScenario.func2
2 scenarios (2 passed)
4 steps (4 passed)
0s
"""

Scenario: Support of Feature Plus Rule Node with Background and multiple scenarios
Given a feature "features/simple.feature" file:
"""
Feature: simple feature
simple feature description
Rule: simple rule
Background: simple background
Given passing step
And passing step
Scenario: simple scenario
simple scenario description
Given passing step
Then passing step
Scenario: simple second scenario
simple second scenario description
Given passing step
Then passing step
"""
When I run feature suite with formatter "pretty"
Then the rendered output will be as follows:
"""
Feature: simple feature
simple feature description
Rule: simple rule
Background: simple background
Given passing step # suite_context_test.go:0 -> InitializeScenario.func2
And passing step # suite_context_test.go:0 -> InitializeScenario.func2
Scenario: simple scenario # features/simple.feature:9
Given passing step # suite_context_test.go:0 -> InitializeScenario.func2
Then passing step # suite_context_test.go:0 -> InitializeScenario.func2
Scenario: simple second scenario # features/simple.feature:15
Given passing step # suite_context_test.go:0 -> InitializeScenario.func2
Then passing step # suite_context_test.go:0 -> InitializeScenario.func2
2 scenarios (2 passed)
8 steps (8 passed)
0s
"""

Scenario: Support of Feature Plus Scenario Node With Tags
Given a feature "features/simple.feature" file:
"""
Expand Down
61 changes: 58 additions & 3 deletions internal/formatters/fmt_pretty.go
Expand Up @@ -123,6 +123,7 @@ func (f *Pretty) Pending(pickle *messages.Pickle, step *messages.PickleStep, mat
f.printStep(pickle, step)
}

// printFeature prints given feature (with title and description) to f.out.
func (f *Pretty) printFeature(feature *messages.Feature) {
fmt.Fprintln(f.out, keywordAndName(feature.Keyword, feature.Name))
if strings.TrimSpace(feature.Description) != "" {
Expand All @@ -132,6 +133,7 @@ func (f *Pretty) printFeature(feature *messages.Feature) {
}
}

// keywordAndName returns formatted keyword and name.
func keywordAndName(keyword, name string) string {
title := whiteb(keyword + ":")
if len(name) > 0 {
Expand All @@ -140,8 +142,11 @@ func keywordAndName(keyword, name string) string {
return title
}

// scenarioLengths returns the length of the scenario header, and the maximum
// length of all steps.
func (f *Pretty) scenarioLengths(pickle *messages.Pickle) (scenarioHeaderLength int, maxLength int) {
feature := f.Storage.MustGetFeature(pickle.Uri)
astRule := feature.FindRule(pickle.AstNodeIds[0])
astScenario := feature.FindScenario(pickle.AstNodeIds[0])
astBackground := feature.FindBackground(pickle.AstNodeIds[0])

Expand All @@ -151,24 +156,42 @@ func (f *Pretty) scenarioLengths(pickle *messages.Pickle) (scenarioHeaderLength
if astBackground != nil {
maxLength = f.longestStep(astBackground.Steps, maxLength)
}
if astRule != nil {
for _, rc := range astRule.Children {
if rc.Scenario != nil {
scenarioHeaderLength = f.lengthPickle(astScenario.Keyword, astScenario.Name)
maxLength = f.longestStep(rc.Scenario.Steps, maxLength)
} else if rc.Background != nil {
maxLength = f.longestStep(rc.Background.Steps, maxLength)
}
}
}

return scenarioHeaderLength, maxLength
}

// printScenarioHeader prints scenario header (keyword/name) with feature line
// reference. The scenario is prefixed with whitespace equal to spaceFilling.
func (f *Pretty) printScenarioHeader(pickle *messages.Pickle, astScenario *messages.Scenario, spaceFilling int) {
feature := f.Storage.MustGetFeature(pickle.Uri)
text := s(f.indent) + keywordAndName(astScenario.Keyword, astScenario.Name)
text += s(spaceFilling) + line(feature.Uri, astScenario.Location)
fmt.Fprintln(f.out, "\n"+text)
}

// printUndefinedPickle prints pickles that are not defined yet.
func (f *Pretty) printUndefinedPickle(pickle *messages.Pickle) {
feature := f.Storage.MustGetFeature(pickle.Uri)
astRule := feature.FindRule(pickle.AstNodeIds[0])
astScenario := feature.FindScenario(pickle.AstNodeIds[0])
astBackground := feature.FindBackground(pickle.AstNodeIds[0])

scenarioHeaderLength, maxLength := f.scenarioLengths(pickle)

if astRule != nil {
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astRule.Keyword, astRule.Name))
}

if astBackground != nil {
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astBackground.Keyword, astBackground.Name))
for _, step := range astBackground.Steps {
Expand Down Expand Up @@ -352,6 +375,7 @@ func (f *Pretty) printTableHeader(row *messages.TableRow, max []int) {

func (f *Pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleStep) {
feature := f.Storage.MustGetFeature(pickle.Uri)
astRule := feature.FindRule(pickle.AstNodeIds[0])
astBackground := feature.FindBackground(pickle.AstNodeIds[0])
astScenario := feature.FindScenario(pickle.AstNodeIds[0])
astStep := feature.FindStep(pickleStep.AstNodeIds[0])
Expand All @@ -378,6 +402,9 @@ func (f *Pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleS
}

if astBackgroundStep && firstExecutedBackgroundStep {
if astRule != nil {
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astRule.Keyword, astRule.Name))
}
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astBackground.Keyword, astBackground.Name))
}

Expand All @@ -391,6 +418,28 @@ func (f *Pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleS

firstExecutedScenarioStep := astScenario.Steps[0].Id == pickleStep.AstNodeIds[0]
if !astBackgroundStep && firstExecutedScenarioStep {
// The first scenario step is responsible for printing the rule unless
// it has already been printed by the background.
if astRule != nil {
var firstScenarioOfRule bool
var ruleHasBackground bool
for _, rc := range astRule.Children {
if sc := rc.Scenario; sc != nil {
firstScenarioOfRule = sc.Id == astScenario.Id
break
}
}
for _, rc := range astRule.Children {
if bc := rc.Background; bc != nil {
ruleHasBackground = true
break
}
}
if firstScenarioOfRule && !ruleHasBackground {
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astRule.Keyword, astRule.Name))
}
}

f.printScenarioHeader(pickle, astScenario, maxLength-scenarioHeaderLength)
}

Expand Down Expand Up @@ -421,6 +470,7 @@ func (f *Pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleS
}
}

// printDocString prints a formatted docString to f.out.
func (f *Pretty) printDocString(docString *messages.DocString) {
var ct string

Expand All @@ -437,7 +487,7 @@ func (f *Pretty) printDocString(docString *messages.DocString) {
fmt.Fprintln(f.out, s(f.indent*3)+cyan(docString.Delimiter))
}

// print table with aligned table cells
// printTable prints table with aligned table cells
// @TODO: need to make example header cells bold
func (f *Pretty) printTable(t *messages.PickleTable, c colors.ColorFunc) {
maxColLengths := maxColLengths(t, c)
Expand All @@ -454,7 +504,7 @@ func (f *Pretty) printTable(t *messages.PickleTable, c colors.ColorFunc) {
}
}

// longest gives a list of longest columns of all rows in Table
// maxColLengths returns a list of longest columns of all rows in Table
func maxColLengths(t *messages.PickleTable, clrs ...colors.ColorFunc) []int {
if t == nil {
return []int{}
Expand All @@ -480,6 +530,7 @@ func maxColLengths(t *messages.PickleTable, clrs ...colors.ColorFunc) []int {
return longest
}

// longestExampleRow returns a list of longest example rows
func longestExampleRow(t *messages.Examples, clrs ...colors.ColorFunc) []int {
if t == nil {
return []int{}
Expand Down Expand Up @@ -519,6 +570,8 @@ func longestExampleRow(t *messages.Examples, clrs ...colors.ColorFunc) []int {
return longest
}

// longestStep returns the length of the longest step in given steps, or
// pickleLength if that is greater.
func (f *Pretty) longestStep(steps []*messages.Step, pickleLength int) int {
max := pickleLength

Expand All @@ -532,14 +585,16 @@ func (f *Pretty) longestStep(steps []*messages.Step, pickleLength int) int {
return max
}

// a line number representation in feature file
// line returns a line number representation in feature file
func line(path string, loc *messages.Location) string {
// Path can contain a line number already.
// This line number has to be trimmed to avoid duplication.
path = strings.TrimSuffix(path, fmt.Sprintf(":%d", loc.Line))
return " " + blackb(fmt.Sprintf("# %s:%d", path, loc.Line))
}

// lengthPickleStep returns the length of a pickle step. The length is
// calculated based on indent, keyword, and associated text.
func (f *Pretty) lengthPickleStep(keyword, text string) int {
return f.indent*2 + utf8.RuneCountInString(strings.TrimSpace(keyword)+" "+text)
}
Expand Down

0 comments on commit e19f7af

Please sign in to comment.