Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions TickSpec.Tests/EscapedPipe.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
Feature: Escaped characters in table cells

Scenario Outline: Values with escaped pipes are parsed correctly
Given a value <value>
Then the value is <expected>

Examples:
| value | expected |
| no pipe | no pipe |
| before\|after | before\|after |
| \|leading | \|leading |
| trailing\| | trailing\| |
| a\|b\|c | a\|b\|c |

Scenario Outline: Values with escaped backslashes are parsed correctly
Given a value <value>
Then the value is <expected>

Examples:
| value | expected |
| hello\\world | hello\\world |

Scenario Outline: Values with escaped newlines are parsed correctly
Given a value <value>
Then the value is <expected>

Examples:
| value | expected |
| line1\nline2 | line1\nline2 |

Scenario: Step table with escaped pipes
Given a table with escaped pipes
| col1 | col2 |
| hello\|world | normal |
| a\|b\|c | x |
Then the table cell 0,0 is hello|world
And the table cell 0,1 is normal
And the table cell 1,0 is a|b|c

Scenario: Step table with escaped backslashes
Given a table with escaped backslashes
| col1 | col2 |
| hello\\world | normal |
Then the table cell 0,0 is hello\world
And the table cell 0,1 is normal

Scenario: Escaped backslash before pipe acts as cell delimiter
Given a table with escaped backslash before pipe
| col1 | col2 | col3 |
| before\\| after | end |
Then the table cell 0,0 is before\
And the table cell 0,1 is after
And the table cell 0,2 is end

Scenario: Triple backslash-pipe is escaped backslash then escaped pipe
Given a table with triple backslash-pipe
| col1 | col2 |
| a\\\|b | normal |
Then the table cell 0,0 is a\|b
And the table cell 0,1 is normal

Scenario: Escaped backslash before n is literal backslash-n not newline
Given a table with escaped backslash before n
| col1 | col2 |
| test\\n | normal |
Then the table cell 0,0 is test\n
And the table cell 0,1 is normal
149 changes: 148 additions & 1 deletion TickSpec.Tests/FeatureParserTest.fs
Original file line number Diff line number Diff line change
Expand Up @@ -604,4 +604,151 @@ let RuleKeyword_ParseBlocks () =
let secondRule = featureBlock.Rules.[1]
Assert.AreEqual("Second business rule", secondRule.Name)
Assert.AreEqual(0, secondRule.Background.Length) // No rule-level background
Assert.AreEqual(1, secondRule.Scenarios.Length) // One scenario in second rule
Assert.AreEqual(1, secondRule.Scenarios.Length) // One scenario in second rule

[<Test>]
let EscapedCharacters_ParseLines () =
"TickSpec.Tests.EscapedPipe.feature"
|> loadFeatureFile
|> verifyLineParsing <|
[
FileStart
FeatureName "Escaped characters in table cells"

// Scenario Outline: escaped pipes
Scenario "Scenario Outline: Values with escaped pipes are parsed correctly"
Step (GivenStep "a value <value>")
Step (ThenStep "the value is <expected>")
Examples
Item (Examples, TableRow [ "value"; "expected" ])
Item (Examples, TableRow [ "no pipe"; "no pipe" ])
Item (Examples, TableRow [ "before|after"; "before|after" ])
Item (Examples, TableRow [ "|leading"; "|leading" ])
Item (Examples, TableRow [ "trailing|"; "trailing|" ])
Item (Examples, TableRow [ "a|b|c"; "a|b|c" ])

// Scenario Outline: escaped backslashes
Scenario "Scenario Outline: Values with escaped backslashes are parsed correctly"
Step (GivenStep "a value <value>")
Step (ThenStep "the value is <expected>")
Examples
Item (Examples, TableRow [ "value"; "expected" ])
Item (Examples, TableRow [ "hello\\world"; "hello\\world" ])

// Scenario Outline: escaped newlines
Scenario "Scenario Outline: Values with escaped newlines are parsed correctly"
Step (GivenStep "a value <value>")
Step (ThenStep "the value is <expected>")
Examples
Item (Examples, TableRow [ "value"; "expected" ])
Item (Examples, TableRow [ "line1\nline2"; "line1\nline2" ])

// Scenario: step table with escaped pipes
Scenario "Scenario: Step table with escaped pipes"
Step (GivenStep "a table with escaped pipes")
Item (Step (GivenStep "a table with escaped pipes"), TableRow [ "col1"; "col2" ])
Item (Step (GivenStep "a table with escaped pipes"), TableRow [ "hello|world"; "normal" ])
Item (Step (GivenStep "a table with escaped pipes"), TableRow [ "a|b|c"; "x" ])
Step (ThenStep "the table cell 0,0 is hello|world")
Step (ThenStep "the table cell 0,1 is normal")
Step (ThenStep "the table cell 1,0 is a|b|c")

// Scenario: step table with escaped backslashes
Scenario "Scenario: Step table with escaped backslashes"
Step (GivenStep "a table with escaped backslashes")
Item (Step (GivenStep "a table with escaped backslashes"), TableRow [ "col1"; "col2" ])
Item (Step (GivenStep "a table with escaped backslashes"), TableRow [ "hello\\world"; "normal" ])
Step (ThenStep "the table cell 0,0 is hello\\world")
Step (ThenStep "the table cell 0,1 is normal")

// Scenario: escaped backslash before pipe acts as delimiter
Scenario "Scenario: Escaped backslash before pipe acts as cell delimiter"
Step (GivenStep "a table with escaped backslash before pipe")
Item (Step (GivenStep "a table with escaped backslash before pipe"), TableRow [ "col1"; "col2"; "col3" ])
Item (Step (GivenStep "a table with escaped backslash before pipe"), TableRow [ "before\\"; "after"; "end" ])
Step (ThenStep "the table cell 0,0 is before\\")
Step (ThenStep "the table cell 0,1 is after")
Step (ThenStep "the table cell 0,2 is end")

// Scenario: triple backslash-pipe (\\\| -> \|)
Scenario "Scenario: Triple backslash-pipe is escaped backslash then escaped pipe"
Step (GivenStep "a table with triple backslash-pipe")
Item (Step (GivenStep "a table with triple backslash-pipe"), TableRow [ "col1"; "col2" ])
Item (Step (GivenStep "a table with triple backslash-pipe"), TableRow [ "a\\|b"; "normal" ])
Step (ThenStep "the table cell 0,0 is a\\|b")
Step (ThenStep "the table cell 0,1 is normal")

// Scenario: escaped backslash before n (\\n -> \n literal, not newline)
Scenario "Scenario: Escaped backslash before n is literal backslash-n not newline"
Step (GivenStep "a table with escaped backslash before n")
Item (Step (GivenStep "a table with escaped backslash before n"), TableRow [ "col1"; "col2" ])
Item (Step (GivenStep "a table with escaped backslash before n"), TableRow [ "test\\n"; "normal" ])
Step (ThenStep "the table cell 0,0 is test\\n")
Step (ThenStep "the table cell 0,1 is normal")
]

[<Test>]
let EscapedCharacters_ParseFeature () =
let featureSource =
"TickSpec.Tests.EscapedPipe.feature"
|> loadFeatureFile
|> FeatureParser.parseFeature

Assert.AreEqual("Escaped characters in table cells", featureSource.Name)

// Outline 1: escaped pipes (5 data rows -> scenarios 0-4)
let scenario1 = featureSource.Scenarios.[1]
let params1 = scenario1.Parameters |> dict
Assert.AreEqual("before|after", params1.["value"])
Assert.AreEqual("before|after", params1.["expected"])

let scenario4 = featureSource.Scenarios.[4]
let params4 = scenario4.Parameters |> dict
Assert.AreEqual("a|b|c", params4.["value"])
Assert.AreEqual("a|b|c", params4.["expected"])

// Outline 2: escaped backslashes (1 data row -> scenario 5)
let bsScenario = featureSource.Scenarios.[5]
let bsParams = bsScenario.Parameters |> dict
Assert.AreEqual("hello\\world", bsParams.["value"])
Assert.AreEqual("hello\\world", bsParams.["expected"])

// Outline 3: escaped newlines (1 data row -> scenario 6)
let nlScenario = featureSource.Scenarios.[6]
let nlParams = nlScenario.Parameters |> dict
Assert.AreEqual("line1\nline2", nlParams.["value"])
Assert.AreEqual("line1\nline2", nlParams.["expected"])

// Scenario: step table with escaped pipes (scenario 7)
let pipeTableScenario = featureSource.Scenarios.[7]
let pipeTable = (pipeTableScenario.Steps.[0] |> snd).Table.Value
Assert.AreEqual([| "col1"; "col2" |], pipeTable.Header)
Assert.AreEqual("hello|world", pipeTable.Rows.[0].[0])
Assert.AreEqual("normal", pipeTable.Rows.[0].[1])
Assert.AreEqual("a|b|c", pipeTable.Rows.[1].[0])

// Scenario: step table with escaped backslashes (scenario 8)
let bsTableScenario = featureSource.Scenarios.[8]
let bsTable = (bsTableScenario.Steps.[0] |> snd).Table.Value
Assert.AreEqual("hello\\world", bsTable.Rows.[0].[0])
Assert.AreEqual("normal", bsTable.Rows.[0].[1])

// Scenario: escaped backslash before pipe = cell delimiter (scenario 9)
let delimScenario = featureSource.Scenarios.[9]
let delimTable = (delimScenario.Steps.[0] |> snd).Table.Value
Assert.AreEqual([| "col1"; "col2"; "col3" |], delimTable.Header)
Assert.AreEqual("before\\", delimTable.Rows.[0].[0])
Assert.AreEqual("after", delimTable.Rows.[0].[1])
Assert.AreEqual("end", delimTable.Rows.[0].[2])

// Scenario: triple backslash-pipe \\\| -> \| (scenario 10)
let tripleScenario = featureSource.Scenarios.[10]
let tripleTable = (tripleScenario.Steps.[0] |> snd).Table.Value
Assert.AreEqual("a\\|b", tripleTable.Rows.[0].[0])
Assert.AreEqual("normal", tripleTable.Rows.[0].[1])

// Scenario: \\n -> literal \n, not newline (scenario 11)
let bsNScenario = featureSource.Scenarios.[11]
let bsNTable = (bsNScenario.Steps.[0] |> snd).Table.Value
Assert.AreEqual("test\\n", bsNTable.Rows.[0].[0])
Assert.IsFalse(bsNTable.Rows.[0].[0].Contains("\n"), "Should not contain actual newline")
1 change: 1 addition & 0 deletions TickSpec.Tests/TickSpec.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<EmbeddedResource Include="TagsAndExamples.feature" />
<EmbeddedResource Include="PlaceholdersInItems.feature" />
<EmbeddedResource Include="RuleKeyword.feature" />
<EmbeddedResource Include="EscapedPipe.feature" />
<Compile Include="FeatureParserTest.fs" />
</ItemGroup>
<ItemGroup>
Expand Down
17 changes: 15 additions & 2 deletions TickSpec/LineParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,21 @@ let (|ButLine|_|) s =
|> Option.map (function Trim t -> ButLine t)
let (|TableRowLine|_|) (s:string) =
if s.Trim().StartsWith("|") then
let columnsStrings = s.Trim().Split([|'|'|], System.StringSplitOptions.RemoveEmptyEntries)
let columns = [ for (Trim s) in columnsStrings -> s ]
let escapedBackslash = "\u0000"
let escapedPipe = "\u0001"
let escapedNewline = "\u0002"
let escaped =
s.Trim()
.Replace("\\\\", escapedBackslash)
.Replace("\\|", escapedPipe)
.Replace("\\n", escapedNewline)
let columnsStrings = escaped.Split([|'|'|], System.StringSplitOptions.RemoveEmptyEntries)
let columns =
[ for (Trim s) in columnsStrings ->
s
.Replace(escapedBackslash, "\\")
.Replace(escapedPipe, "|")
.Replace(escapedNewline, "\n") ]
TableRowLine columns |> Some
else None
let (|Bullet|_|) (s:string) =
Expand Down
Loading