From 3fb59fa911e650af5d1d8d206dba1a4eadb71700 Mon Sep 17 00:00:00 2001 From: Alvaro Vilanova Vidal Date: Mon, 2 Feb 2026 15:33:15 +0100 Subject: [PATCH] fix: attach tables and doc strings to correct step --- src/dream_test/gherkin/parser.gleam | 14 +-- .../dream_test/gherkin/multi_table_test.gleam | 103 ++++++++++++++++++ test/fixtures/multi_table_test.feature | 11 ++ 3 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 test/dream_test/gherkin/multi_table_test.gleam create mode 100644 test/fixtures/multi_table_test.feature diff --git a/src/dream_test/gherkin/parser.gleam b/src/dream_test/gherkin/parser.gleam index bd5d17f..1ec9ceb 100644 --- a/src/dream_test/gherkin/parser.gleam +++ b/src/dream_test/gherkin/parser.gleam @@ -198,15 +198,14 @@ fn close_doc_string(state: ParserState) -> Result(ParserState, String) { let doc_string = DocString(content: content, content_type: state.doc_string_type) - // Attach to the last step - case list.reverse(state.current_steps) { + // Attach to the most recent step (head of list, since steps are prepended) + case state.current_steps { [Step(keyword, text, _), ..rest] -> { let updated_step = Step(keyword, text, Some(doc_string)) - let new_steps = list.reverse([updated_step, ..rest]) Ok( ParserState( ..state, - current_steps: new_steps, + current_steps: [updated_step, ..rest], in_doc_string: False, doc_string_content: [], doc_string_type: None, @@ -519,8 +518,8 @@ fn add_data_table_row( cells: List(String), state: ParserState, ) -> Result(ParserState, String) { - // Add to the last step's DataTable - case list.reverse(state.current_steps) { + // Add to the most recent step's DataTable (head of list, since steps are prepended) + case state.current_steps { [Step(keyword, text, arg), ..rest] -> { let updated_arg = case arg { Some(DataTable(rows)) -> DataTable(rows: list.append(rows, [cells])) @@ -528,8 +527,7 @@ fn add_data_table_row( None -> DataTable(rows: [cells]) } let updated_step = Step(keyword, text, Some(updated_arg)) - let new_steps = list.reverse([updated_step, ..rest]) - Ok(ParserState(..state, current_steps: new_steps)) + Ok(ParserState(..state, current_steps: [updated_step, ..rest])) } [] -> Error("DataTable without preceding step") } diff --git a/test/dream_test/gherkin/multi_table_test.gleam b/test/dream_test/gherkin/multi_table_test.gleam new file mode 100644 index 0000000..fb91a52 --- /dev/null +++ b/test/dream_test/gherkin/multi_table_test.gleam @@ -0,0 +1,103 @@ +//// Test that multiple steps with tables work correctly + +import dream_test/gherkin/parser +import dream_test/gherkin/types as gtypes +import dream_test/matchers.{fail_with} +import dream_test/types.{AssertionOk} +import dream_test/unit.{describe, it} +import gleam/option.{Some} +import gleam/result + +pub fn tests() { + describe("Multiple tables in steps", [ + it("parser correctly parses multiple tables", fn() { + let content = + "Feature: Multi Table\n" + <> "\n" + <> " Scenario: Two tables\n" + <> " Given the first table:\n" + <> " | A | B |\n" + <> " | 1 | 2 |\n" + <> " When the second table:\n" + <> " | X | Y |\n" + <> " | 3 | 4 |\n" + + let validation_result = { + use feature <- result.try(parse_content(content)) + use scenario <- result.try(get_single_scenario(feature)) + use #(step1, step2) <- result.try(get_two_steps(scenario)) + use table1 <- result.try(get_step_table(step1, "Step 1")) + use table2 <- result.try(get_step_table(step2, "Step 2")) + use _ <- result.try(verify_table(table1, expected_table1(), "First")) + use _ <- result.try(verify_table(table2, expected_table2(), "Second")) + Ok(Nil) + } + + case validation_result { + Ok(_) -> Ok(AssertionOk) + Error(message) -> Ok(fail_with(message)) + } + }), + ]) +} + +fn parse_content(content: String) -> Result(gtypes.Feature, String) { + parser.parse_string(content) +} + +fn get_single_scenario( + feature: gtypes.Feature, +) -> Result(gtypes.Scenario, String) { + case feature.scenarios { + [scenario] -> Ok(scenario) + _ -> Error("Expected 1 scenario") + } +} + +fn get_two_steps( + scenario: gtypes.Scenario, +) -> Result(#(gtypes.Step, gtypes.Step), String) { + case scenario { + gtypes.Scenario(_, _, steps) -> extract_two_steps(steps) + gtypes.ScenarioOutline(_, _, _, _) -> + Error("Expected Scenario, got ScenarioOutline") + } +} + +fn extract_two_steps( + steps: List(gtypes.Step), +) -> Result(#(gtypes.Step, gtypes.Step), String) { + case steps { + [step1, step2] -> Ok(#(step1, step2)) + _ -> Error("Expected 2 steps") + } +} + +fn get_step_table( + step: gtypes.Step, + step_name: String, +) -> Result(List(List(String)), String) { + case step.argument { + Some(gtypes.DataTable(rows)) -> Ok(rows) + _ -> Error(step_name <> " has no table") + } +} + +fn verify_table( + actual: List(List(String)), + expected: List(List(String)), + table_name: String, +) -> Result(Nil, String) { + case actual == expected { + True -> Ok(Nil) + False -> Error(table_name <> " table has wrong content") + } +} + +fn expected_table1() -> List(List(String)) { + [["A", "B"], ["1", "2"]] +} + +fn expected_table2() -> List(List(String)) { + [["X", "Y"], ["3", "4"]] +} diff --git a/test/fixtures/multi_table_test.feature b/test/fixtures/multi_table_test.feature new file mode 100644 index 0000000..cf4f2c7 --- /dev/null +++ b/test/fixtures/multi_table_test.feature @@ -0,0 +1,11 @@ +Feature: Multiple Tables Test + Testing that multiple steps can have tables + + Scenario: Two steps with tables + Given the first table: + | A | B | + | 1 | 2 | + When the second table: + | X | Y | + | 3 | 4 | + Then both tables were received