From df59ee793bbb8ca235a3d89fb9ea9dbec7dee9c1 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 19 Sep 2025 15:52:38 -0400 Subject: [PATCH 1/6] Add `use_snapshot()` Implement a `use_snapshot()` helper to go with `use_r()` and `use_test()`. I ran out of time and didn't implement tests or NEWS for this yet (and there's a solid chance that this doesn't quite nail it), but I wanted to get it checked in. Fixes #2156. --- NAMESPACE | 1 + R/r.R | 38 +++++++++++++++++++++++++++++++++++++- man/use_snapshot.Rd | 20 ++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 man/use_snapshot.Rd diff --git a/NAMESPACE b/NAMESPACE index 91a15d28e..dd0b8bdc5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -166,6 +166,7 @@ export(use_rmarkdown_template) export(use_roxygen_md) export(use_rstudio) export(use_rstudio_preferences) +export(use_snapshot) export(use_spell_check) export(use_standalone) export(use_template) diff --git a/R/r.R b/R/r.R index 5b865ee83..2477b7d80 100644 --- a/R/r.R +++ b/R/r.R @@ -88,6 +88,39 @@ use_test <- function(name = NULL, open = rlang::is_interactive()) { invisible(TRUE) } +#' Edit the snapshot file associated with an R or test file +#' +#' This function opens the snapshot file associated with an R or test file, if +#' one exists. +#' +#' @inheritParams use_r +#' @param variant An optional subdirectory within the `_snaps/` directory. +#' @export +use_snapshot <- function( + name = NULL, + variant = NULL, + open = rlang::is_interactive() +) { + if (!uses_testthat()) { + use_testthat_impl() + } + + path_root <- path("tests", "testthat", "_snaps") + if (!is.null(variant)) { + path_root <- path(path_root, variant) + } + # I can't pass "md" to `compute_name()` because we're fine with them giving us + # "R" as the extension here. + path <- path(path_root, basename(compute_name(name)), ext = "md") + + if (!file_exists(path)) { + cli::cli_abort("No snapshot file exists for {.arg {name}}.") + } + edit_file(proj_path(path), open = open) + + invisible(TRUE) +} + #' Create or edit a test helper file #' #' This function creates (or opens) a test helper file, typically @@ -183,7 +216,10 @@ compute_active_name <- function(path, ext, error_call = caller_env()) { path <- proj_path_prep(path_expand_r(path)) dir <- path_dir(proj_rel_path(path)) - if (!dir %in% c("R", "src", "tests/testthat", "tests/testthat/_snaps")) { + if ( + !dir %in% c("R", "src", "tests/testthat", "tests/testthat/_snaps") && + !grepl("tests/testthat/_snaps", dir) + ) { cli::cli_abort( "Open file must be code, test, or snapshot.", call = error_call diff --git a/man/use_snapshot.Rd b/man/use_snapshot.Rd new file mode 100644 index 000000000..b9e1e4f76 --- /dev/null +++ b/man/use_snapshot.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/r.R +\name{use_snapshot} +\alias{use_snapshot} +\title{Edit the snapshot file associated with an R or test file} +\usage{ +use_snapshot(name = NULL, variant = NULL, open = rlang::is_interactive()) +} +\arguments{ +\item{name}{Either a string giving a file name (without directory) or +\code{NULL} to take the name from the currently open file in RStudio.} + +\item{variant}{An optional subdirectory within the \verb{_snaps/} directory.} + +\item{open}{Whether to open the file for interactive editing.} +} +\description{ +This function opens the snapshot file associated with an R or test file, if +one exists. +} From 8ab931d7f446aaf9aa00f5ec3fd4e57cc86d7f02 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 20 Sep 2025 07:34:55 -0400 Subject: [PATCH 2/6] Fix path calculation and remove basename usage. --- R/r.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/r.R b/R/r.R index 2477b7d80..1c8264ad3 100644 --- a/R/r.R +++ b/R/r.R @@ -111,7 +111,7 @@ use_snapshot <- function( } # I can't pass "md" to `compute_name()` because we're fine with them giving us # "R" as the extension here. - path <- path(path_root, basename(compute_name(name)), ext = "md") + path <- path(path_root, fs::path_ext_set(compute_name(NULL), "md")) if (!file_exists(path)) { cli::cli_abort("No snapshot file exists for {.arg {name}}.") From fa71a445e89b024b98953f71309207f21e4c862f Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 20 Sep 2025 08:04:04 -0400 Subject: [PATCH 3/6] Add tests and NEWS. --- NEWS.md | 1 + R/r.R | 6 +++--- tests/testthat/_snaps/r.md | 8 ++++++++ tests/testthat/test-r.R | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 37d7ede9f..54295b987 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ # usethis (development version) * `pr_resume()` (without a specific `branch`) and `pr_fetch()` (without a specific `number`) no longer error when a branch name contains curly braces (#2107, @jonthegeek). +* `use_snapshot()` is a new function to open a [testthat snapshot file]() corresponding to a given test file or R file (#2156, @jonthegeek). # usethis 3.2.1 diff --git a/R/r.R b/R/r.R index 1c8264ad3..59884d683 100644 --- a/R/r.R +++ b/R/r.R @@ -105,18 +105,18 @@ use_snapshot <- function( use_testthat_impl() } - path_root <- path("tests", "testthat", "_snaps") + path_root <- proj_path("tests", "testthat", "_snaps") if (!is.null(variant)) { path_root <- path(path_root, variant) } # I can't pass "md" to `compute_name()` because we're fine with them giving us # "R" as the extension here. - path <- path(path_root, fs::path_ext_set(compute_name(NULL), "md")) + path <- path(path_root, fs::path_ext_set(compute_name(name), "md")) if (!file_exists(path)) { cli::cli_abort("No snapshot file exists for {.arg {name}}.") } - edit_file(proj_path(path), open = open) + edit_file(path, open = open) invisible(TRUE) } diff --git a/tests/testthat/_snaps/r.md b/tests/testthat/_snaps/r.md index f202815b0..61c6f66b4 100644 --- a/tests/testthat/_snaps/r.md +++ b/tests/testthat/_snaps/r.md @@ -1,3 +1,11 @@ +# use_snapshot() errors for non-existent snapshot file + + Code + use_snapshot("foo", open = FALSE) + Condition + Error in `use_snapshot()`: + ! No snapshot file exists for `foo`. + # use_test_helper() creates a helper file Code diff --git a/tests/testthat/test-r.R b/tests/testthat/test-r.R index 6839f088f..37059b36b 100644 --- a/tests/testthat/test-r.R +++ b/tests/testthat/test-r.R @@ -10,6 +10,24 @@ test_that("use_test() creates a test file", { expect_proj_file("tests", "testthat", "test-foo.R") }) +test_that("use_snapshot() errors for non-existent snapshot file", { + create_local_package() + expect_snapshot( + error = TRUE, + use_snapshot("foo", open = FALSE) + ) +}) + +test_that("use_snapshot() works for existing snapshot file", { + create_local_package() + path <- proj_path("tests", "testthat", "_snaps", "foo.md") + use_directory(path("tests", "testthat", "_snaps")) + write_utf8(path, "## Snapshot for foo") + expect_no_error( + use_snapshot("foo", open = FALSE) + ) +}) + test_that("use_test_helper() creates a helper file", { create_local_package() From 9afc67eb059c13f18019a4554fc8a1a6248aa256 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 20 Sep 2025 08:09:33 -0400 Subject: [PATCH 4/6] Explain extra clause in active file if. --- R/r.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/r.R b/R/r.R index 59884d683..1cfbde5c9 100644 --- a/R/r.R +++ b/R/r.R @@ -218,6 +218,7 @@ compute_active_name <- function(path, ext, error_call = caller_env()) { dir <- path_dir(proj_rel_path(path)) if ( !dir %in% c("R", "src", "tests/testthat", "tests/testthat/_snaps") && + # This makes sure variants are also supported. !grepl("tests/testthat/_snaps", dir) ) { cli::cli_abort( From ee9dc282769127b9e46b3815828ab949daa4ebf1 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 20 Sep 2025 08:27:45 -0400 Subject: [PATCH 5/6] Add `use_snapshot` to `_pkgdown.yml` --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index 6055e2218..63b525161 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -56,6 +56,7 @@ reference: - use_import_from - use_r - use_rmarkdown_template + - use_snapshot - use_spell_check - use_test - use_test_helper From 7ae4acc39051fdaacb1596dbabc0f4e6d918a907 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 26 Sep 2025 15:59:33 -0500 Subject: [PATCH 6/6] Apply review suggestions. --- R/r.R | 13 +++---------- man/use_snapshot.Rd | 4 +--- tests/testthat/_snaps/r.md | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/R/r.R b/R/r.R index 1cfbde5c9..29d28ef85 100644 --- a/R/r.R +++ b/R/r.R @@ -94,27 +94,20 @@ use_test <- function(name = NULL, open = rlang::is_interactive()) { #' one exists. #' #' @inheritParams use_r -#' @param variant An optional subdirectory within the `_snaps/` directory. #' @export use_snapshot <- function( name = NULL, - variant = NULL, open = rlang::is_interactive() ) { if (!uses_testthat()) { use_testthat_impl() } - path_root <- proj_path("tests", "testthat", "_snaps") - if (!is.null(variant)) { - path_root <- path(path_root, variant) - } - # I can't pass "md" to `compute_name()` because we're fine with them giving us - # "R" as the extension here. - path <- path(path_root, fs::path_ext_set(compute_name(name), "md")) + snap_name <- compute_name(name, ext = "md") + path <- proj_path("tests", "testthat", "_snaps", snap_name) if (!file_exists(path)) { - cli::cli_abort("No snapshot file exists for {.arg {name}}.") + cli::cli_abort("No snapshot file exists for {.var {snap_name}}.") } edit_file(path, open = open) diff --git a/man/use_snapshot.Rd b/man/use_snapshot.Rd index b9e1e4f76..ee018be9c 100644 --- a/man/use_snapshot.Rd +++ b/man/use_snapshot.Rd @@ -4,14 +4,12 @@ \alias{use_snapshot} \title{Edit the snapshot file associated with an R or test file} \usage{ -use_snapshot(name = NULL, variant = NULL, open = rlang::is_interactive()) +use_snapshot(name = NULL, open = rlang::is_interactive()) } \arguments{ \item{name}{Either a string giving a file name (without directory) or \code{NULL} to take the name from the currently open file in RStudio.} -\item{variant}{An optional subdirectory within the \verb{_snaps/} directory.} - \item{open}{Whether to open the file for interactive editing.} } \description{ diff --git a/tests/testthat/_snaps/r.md b/tests/testthat/_snaps/r.md index 61c6f66b4..925a0f9f1 100644 --- a/tests/testthat/_snaps/r.md +++ b/tests/testthat/_snaps/r.md @@ -4,7 +4,7 @@ use_snapshot("foo", open = FALSE) Condition Error in `use_snapshot()`: - ! No snapshot file exists for `foo`. + ! No snapshot file exists for `foo.md`. # use_test_helper() creates a helper file