From 5cae7ba34fdd2d3e6f1c3d865f37f88d28290eb8 Mon Sep 17 00:00:00 2001 From: Jakub Sobolewski Date: Tue, 18 Feb 2025 15:47:20 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20allow=20setting=20feature=20fil?= =?UTF-8?q?e=20indentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DESCRIPTION | 1 + NAMESPACE | 1 + R/options.R | 26 ++++++++++++++++ R/test.R | 1 + R/tokenize.R | 3 +- R/validate.R | 25 ++++++++++++++++ man/opts.Rd | 26 ++++++++++++++++ man/validate_feature.Rd | 12 ++++++++ tests/testthat/_snaps/examples.md | 16 +++++----- tests/testthat/_snaps/validate.md | 5 ++++ tests/testthat/test-tokenize.R | 49 +++++++++++++++++++++++++++++++ tests/testthat/test-validate.R | 33 +++++++++++++++++++++ 12 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 R/options.R create mode 100644 R/validate.R create mode 100644 man/opts.Rd create mode 100644 man/validate_feature.Rd create mode 100644 tests/testthat/_snaps/validate.md create mode 100644 tests/testthat/test-validate.R diff --git a/DESCRIPTION b/DESCRIPTION index fc53886..2412e5b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,6 +24,7 @@ Suggests: Config/testthat/edition: 3 Imports: checkmate, + cli, dplyr, fs, glue, diff --git a/NAMESPACE b/NAMESPACE index 273fc1c..0817050 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,7 @@ importFrom(checkmate,assert_string) importFrom(checkmate,assert_subset) importFrom(checkmate,test_character) importFrom(checkmate,test_subset) +importFrom(cli,cli_abort) importFrom(dplyr,bind_cols) importFrom(fs,dir_ls) importFrom(glue,glue) diff --git a/R/options.R b/R/options.R new file mode 100644 index 0000000..a3a0e76 --- /dev/null +++ b/R/options.R @@ -0,0 +1,26 @@ +#' {cucumber} Options +#' +#' Internally used, package-specific options. +#' They allow overriding the default behavior of the package. +#' +#' @details +#' +#' The following options are available: +#' +#' - `cucumber.indent` +#' +#' Regular expression for the indent of the feature files. +#' +#' default: `^\\s{2}` +#' +#' - `cucumber.interactive` +#' +#' Logical value indicating whether to ask which feature files to run. +#' +#' default: `FALSE` +#' +#' See [base::options()] and [base::getOption()] on how to work with options. +#' +#' @md +#' @name opts +NULL diff --git a/R/test.R b/R/test.R index d1be294..1cf99fc 100644 --- a/R/test.R +++ b/R/test.R @@ -56,6 +56,7 @@ test <- function( } # nocov end feature_files |> map(readLines) |> + map(validate_feature) |> map(normalize_feature) |> walk(run, steps = steps, parameters = get_parameters()) } diff --git a/R/tokenize.R b/R/tokenize.R index 3fcc818..db6d825 100644 --- a/R/tokenize.R +++ b/R/tokenize.R @@ -1,4 +1,3 @@ -INDENT <- "^\\s{2}" NODE_REGEX <- paste0( "^(?!\\s+)(", paste0( @@ -32,7 +31,7 @@ remove_trailing_colon <- function(x) { #' @importFrom stringr str_remove_all remove_indent <- function(x) { - str_remove_all(x, INDENT) + str_remove_all(x, getOption("cucumber.indent", default = "^\\s{2}")) } #' @importFrom stringr str_detect diff --git a/R/validate.R b/R/validate.R new file mode 100644 index 0000000..9903478 --- /dev/null +++ b/R/validate.R @@ -0,0 +1,25 @@ +#' Validate lines read from a feature file +#' @keywords internal +validate_feature <- function(lines) { + # Remove comments and empty lines for validation + clean_lines <- remove_comments(remove_empty_lines(lines)) + clean_lines |> + validate_indentation() + invisible(lines) +} + +#' @keywords internal +#' @importFrom stringr str_detect +#' @importFrom cli cli_abort +validate_indentation <- function(lines) { + indent <- getOption("cucumber.indent", default = "^\\s{2}") + test_lines <- lines[!str_detect(lines, "^Feature")] |> + remove_empty_lines() + if (any(!str_detect(test_lines, indent))) { + cli_abort(c( + "All lines must be indented with {indent}", + "i" = "Check the {.code getOption('cucumber.indent')} option if it is set to your feature file indent." + )) + } + invisible(lines) +} diff --git a/man/opts.Rd b/man/opts.Rd new file mode 100644 index 0000000..99708ea --- /dev/null +++ b/man/opts.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/options.R +\name{opts} +\alias{opts} +\title{{cucumber} Options} +\description{ +Internally used, package-specific options. +They allow overriding the default behavior of the package. +} +\details{ +The following options are available: +\itemize{ +\item \code{cucumber.indent} + +Regular expression for the indent of the feature files. + +default: \verb{^\\\\s\{2\}} +\item \code{cucumber.interactive} + +Logical value indicating whether to ask which feature files to run. + +default: \code{FALSE} +} + +See \code{\link[base:options]{base::options()}} and \code{\link[base:options]{base::getOption()}} on how to work with options. +} diff --git a/man/validate_feature.Rd b/man/validate_feature.Rd new file mode 100644 index 0000000..4f7c35e --- /dev/null +++ b/man/validate_feature.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/validate.R +\name{validate_feature} +\alias{validate_feature} +\title{Validate lines read from a feature file} +\usage{ +validate_feature(lines) +} +\description{ +Validate lines read from a feature file +} +\keyword{internal} diff --git a/tests/testthat/_snaps/examples.md b/tests/testthat/_snaps/examples.md index 098be8f..244143e 100644 --- a/tests/testthat/_snaps/examples.md +++ b/tests/testthat/_snaps/examples.md @@ -177,16 +177,16 @@ -------------------------------------------------------------------------------- Failure ('test-cucumber.R:1:1'): Scenario: Adding integer and float context$result (`actual`) not equal to `expected` (`expected`). - `actual`: 2 - `expected`: 5 + `actual`: 2.1 + `expected`: 5.0 Backtrace: x 1. \-global ``(expected = 5L, context = ``) 2. \-testthat::expect_equal(context$result, expected) at ./steps/addition.R:7:3 Failure ('test-cucumber.R:1:1'): Scenario: Adding float and float context$result (`actual`) not equal to `expected` (`expected`). - `actual`: 2 - `expected`: 5 + `actual`: 2.2 + `expected`: 5.0 Backtrace: x 1. \-global ``(expected = 5L, context = ``) @@ -207,16 +207,16 @@ -- Failed tests ---------------------------------------------------------------- Failure ('test-cucumber.R:1:1'): Scenario: Adding integer and float context$result (`actual`) not equal to `expected` (`expected`). - `actual`: 2 - `expected`: 5 + `actual`: 2.1 + `expected`: 5.0 Backtrace: x 1. \-global ``(expected = 5L, context = ``) 2. \-testthat::expect_equal(context$result, expected) at ./steps/addition.R:7:3 Failure ('test-cucumber.R:1:1'): Scenario: Adding float and float context$result (`actual`) not equal to `expected` (`expected`). - `actual`: 2 - `expected`: 5 + `actual`: 2.2 + `expected`: 5.0 Backtrace: x 1. \-global ``(expected = 5L, context = ``) diff --git a/tests/testthat/_snaps/validate.md b/tests/testthat/_snaps/validate.md new file mode 100644 index 0000000..8cc9ad0 --- /dev/null +++ b/tests/testthat/_snaps/validate.md @@ -0,0 +1,5 @@ +# validate_feature: should validate indent + + All lines must be indented with ^\s{4} + i Check the `getOption('cucumber.indent')` option if it is set to your feature file indent. + diff --git a/tests/testthat/test-tokenize.R b/tests/testthat/test-tokenize.R index b3c9c22..0b61a25 100644 --- a/tests/testthat/test-tokenize.R +++ b/tests/testthat/test-tokenize.R @@ -454,4 +454,53 @@ describe("parse", { ) ) }) + + it("should parse feature files with 4 spaces indent", { + # Arrange + lines <- c( + "Feature: Guess the word", + " Scenario: Maker starts a game", + " When the Maker starts a game", + " Then the Maker waits for a Breaker to join" + ) + indent <- "^\\s{4}" + + # Act + withr::with_options(list(cucumber.indent = indent), { + result <- tokenize(lines) + }) + + # Assert + expect_equal( + result, + list( + list( + type = "Feature", + value = "Guess the word", + children = list( + list( + type = "Scenario", + value = "Maker starts a game", + children = list( + list( + type = "When", + value = "the Maker starts a game", + children = NULL, + data = NULL + ), + list( + type = "Then", + value = "the Maker waits for a Breaker to join", + children = NULL, + data = NULL + ) + ), + data = NULL + ) + ), + data = NULL + ) + ) + ) + }) }) diff --git a/tests/testthat/test-validate.R b/tests/testthat/test-validate.R new file mode 100644 index 0000000..15be765 --- /dev/null +++ b/tests/testthat/test-validate.R @@ -0,0 +1,33 @@ +describe("validate_feature", { + it("shouldn't produce error feature with valid indentation", { + withr::with_options(list(cucumber.indent = "^\\s{2}"), { + expect_no_error( + validate_feature( + c( + "Feature: foo", + " Scenario: bar", + " Given foo", + " When foo", + " Then foo" + ) + ) + ) + }) + }) + + it("should validate indent", { + withr::with_options(list(cucumber.indent = "^\\s{4}"), { + expect_snapshot_error( + validate_feature( + c( + "Feature: foo", + " Scenario: bar", + " Given foo", + " When foo", + " Then foo" + ) + ) + ) + }) + }) +}) From 2424d3c5066f961708ba9e645b1fdd1ad926f771 Mon Sep 17 00:00:00 2001 From: Jakub Sobolewski Date: Tue, 18 Feb 2025 15:49:22 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9D=20update=20changelog=20and=20b?= =?UTF-8?q?ump=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DESCRIPTION | 2 +- NEWS.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 2412e5b..2a169b4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: cucumber Type: Package Title: Behavior-Driven Development for R -Version: 1.1.0 +Version: 1.1.1 Authors@R: person("Jakub", "Sobolewski", email = "jakupsob@gmail.com", role = c("aut", "cre")) Description: Write executable specifications in a natural language that describes how your code should behave. Write specifications in feature files using 'Gherkin' language and execute them using functions implemented in R. diff --git a/NEWS.md b/NEWS.md index 29ff1a4..06769a6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# cucumber 1.1.1 + +- ✨ Added option to set the indent of feature files. Useful when you use a different indent than the default 2 whitespaces. All user-facing options are documented in `?cucumber::opts`. (#5) +- ✨ Added validation of feature files to check if they have a consistent indentation. (#5) + # cucumber 1.1.0 - ✨ Added scenario `before` and `after` hooks. From c5f1af529632d548105afdc39c74f91dc674f47b Mon Sep 17 00:00:00 2001 From: Jakub Sobolewski Date: Tue, 18 Feb 2025 19:29:52 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=85=20skip=20test=20on=20r-devel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/testthat/test-examples.R | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/testthat/test-examples.R b/tests/testthat/test-examples.R index 5dc6f2b..4187532 100644 --- a/tests/testthat/test-examples.R +++ b/tests/testthat/test-examples.R @@ -36,6 +36,11 @@ describe("test", { }) it("should run with shinytest2", { + # nolint start + # Produces on r-devel: + # Superclass process has cloneable=FALSE, but subclass r_process has cloneable=TRUE. A subclass cannot be cloneable when its superclass is not cloneable, so cloning will be disabled for r_process. + # nolint end + skip_if(R.version$status == "Under development (unstable)") test_example("examples/shinytest2") }) From d5f4d85506e73d6c28ce8b126e9a6e2ea7fbc0fc Mon Sep 17 00:00:00 2001 From: Jakub Sobolewski Date: Tue, 18 Feb 2025 19:54:27 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=91=B7=20run=20checks=20on=20pr=20to?= =?UTF-8?q?=20dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/R-CMD-check.yaml | 4 ++-- .github/workflows/test-coverage.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index f4e0164..06b8610 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -2,9 +2,9 @@ # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: [main, master] + branches: [main, master, dev] pull_request: - branches: [main, master] + branches: [main, master, dev] name: R-CMD-check diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 960234c..3d08384 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -2,9 +2,9 @@ # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: [main, master] + branches: [main, master, dev] pull_request: - branches: [main, master] + branches: [main, master, dev] name: test-coverage