diff --git a/DESCRIPTION b/DESCRIPTION index 4785036..77e93fd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -22,6 +22,7 @@ BugReports: https://github.com/rstudio/reactlog/issues Depends: R (>= 3.0.2) Imports: + cli, jsonlite (>= 0.9.16), rlang, rmarkdown @@ -36,6 +37,7 @@ Suggests: withr VignetteBuilder: knitr +Config/testthat/edition: 3 Config/roxygen2/version: 8.1.1 Encoding: UTF-8 Language: en-US diff --git a/NEWS.md b/NEWS.md index 05f4605..022716a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # reactlog (development) +- Error and warning messages now use cli and name the function you called. Missing or too-old shiny now gives a clear message from `rlang::check_installed()`, including when `reactlog_show()` or `reactlog_widget()` is called with no `log`. cli is now an imported package. + - Added `reactlog_widget()`, which shows the reactlog as an htmlwidget so it can be embedded in R Markdown or Quarto documents. Like `reactlog_show()`, `log` defaults to `shiny::reactlog()`. To show it in a Shiny app, use `reactlog_widget_module_ui()` and `reactlog_widget_module_server()`, or `reactlog_widget_output()` and `render_reactlog_widget()`. Requires the htmlwidgets package (#160) - `reactlog_show()` now uses `shiny::reactlog()` as its default `log`, so `reactlog_show()` works with no arguments after running an app (#91) diff --git a/R/renderReactLog.R b/R/renderReactLog.R index f19c043..ef453ae 100644 --- a/R/renderReactLog.R +++ b/R/renderReactLog.R @@ -14,7 +14,7 @@ reactlog_render <- function(log, session_token = NULL, time = TRUE) { html <- reactlog_page_html(log, session_token = session_token, time = time) # check if shiny is even available - if (requireNamespace("shiny", quietly = TRUE)) { + if (rlang::is_installed("shiny")) { # add resource path to pkg files for shiny # (avoids warning of tmp folder moving around) shiny::addResourcePath( @@ -42,8 +42,13 @@ fixed_sub <- function(...) { } # The reactlog page as one string. Assets are still linked from `reactlogAsset/`. -reactlog_page_html <- function(log, session_token = NULL, time = TRUE) { - log <- reactlog_upgrade(log) +reactlog_page_html <- function( + log, + session_token = NULL, + time = TRUE, + call = rlang::caller_env() +) { + log <- reactlog_upgrade(log, call = call) template_file <- inst_reactlog_file("reactlog.html") # does not need utf8 support diff --git a/R/setReactLog.R b/R/setReactLog.R index de6c27c..37ca2b9 100644 --- a/R/setReactLog.R +++ b/R/setReactLog.R @@ -15,12 +15,14 @@ reactlog_enable <- function() { #' @export #' @rdname setReactLog reactlog_disable <- function(warn = TRUE) { - if (!isTRUE(warn) && !identical(warn, FALSE)) { - stop("'warn' must be either 'TRUE' or 'FALSE'") + if (!rlang::is_bool(warn)) { + cli::cli_abort( + "{.arg warn} must be {.code TRUE} or {.code FALSE}, not {.obj_type_friendly {warn}}." + ) } options(shiny.reactlog = FALSE) if (isTRUE(warn)) { - warning("Please restart R to free up 'reactlog' memory", call. = FALSE) + cli::cli_warn("Please restart R to free up {.pkg reactlog} memory.") } } diff --git a/R/shinyModule.R b/R/shinyModule.R index 42b57fa..aa052b4 100644 --- a/R/shinyModule.R +++ b/R/shinyModule.R @@ -145,9 +145,7 @@ test_shiny_version <- function() { }) } assert_shiny_version <- function() { - if (!test_shiny_version()) { - stop("`shiny` v", shiny_version_required(), " or greater must be installed") - } + rlang::check_installed("shiny", version = as.character(shiny_version_required())) } diff --git a/R/showReactLog.R b/R/showReactLog.R index 80650cd..1f2294d 100644 --- a/R/showReactLog.R +++ b/R/showReactLog.R @@ -61,6 +61,7 @@ #' } reactlog_show <- function(log = shiny::reactlog(), time = TRUE, ...) { + if (missing(log)) rlang::check_installed("shiny") file <- reactlog_render(log, time = as.logical(time), ...) utils::browseURL(file) } diff --git a/R/version.R b/R/version.R index c04eca1..49df0a5 100644 --- a/R/version.R +++ b/R/version.R @@ -1,17 +1,19 @@ -reactlog_upgrade <- function(log) { +reactlog_upgrade <- function(log, call = rlang::caller_env()) { version <- attr(log, "version") if (is.null(version)) { - stop("'log' is missing a 'version' attribute. This is required.") + cli::cli_abort(c( + "{.arg log} is missing a {.field version} attribute.", + "i" = "Use {.fn shiny::reactlog} to get a log." + ), call = call) } version <- as.character(version) if (identical(version, "1")) { return(log) } - stop( - "Versions available: 1\n", - "Latest reactlog version: 1\n", - "Version provided: ", version - ) + cli::cli_abort(c( + "x" = "{.arg log} has version {.val {version}}.", + "i" = "{.pkg reactlog} supports version {.val 1}." + ), call = call) } diff --git a/R/widget.R b/R/widget.R index d8b9033..218cc13 100644 --- a/R/widget.R +++ b/R/widget.R @@ -33,6 +33,7 @@ reactlog_widget <- function( elementId = NULL # nolint: object_name_linter. ) { rlang::check_installed("htmlwidgets") + if (missing(log)) rlang::check_installed("shiny") html <- reactlog_page_html(log, time = as.logical(time), ...) diff --git a/tests/testthat/_snaps/errors.md b/tests/testthat/_snaps/errors.md new file mode 100644 index 0000000..a845223 --- /dev/null +++ b/tests/testthat/_snaps/errors.md @@ -0,0 +1,34 @@ +# reactlog_disable() checks `warn` and warns about memory + + Code + reactlog_disable(warn = "yes") + Condition + Error in `reactlog_disable()`: + ! `warn` must be `TRUE` or `FALSE`, not a string. + +--- + + Code + reactlog_disable() + Condition + Warning: + Please restart R to free up reactlog memory. + +# bad log versions are explained by the function the user called + + Code + reactlog_render(list()) + Condition + Error in `reactlog_render()`: + ! `log` is missing a version attribute. + i Use `shiny::reactlog()` to get a log. + +--- + + Code + reactlog_render(structure(list(), version = "2")) + Condition + Error in `reactlog_render()`: + x `log` has version "2". + i reactlog supports version "1". + diff --git a/tests/testthat/test-errors.R b/tests/testthat/test-errors.R new file mode 100644 index 0000000..29042fe --- /dev/null +++ b/tests/testthat/test-errors.R @@ -0,0 +1,30 @@ +test_that("reactlog_disable() checks `warn` and warns about memory", { + withr::local_options(shiny.reactlog = TRUE) + expect_snapshot(error = TRUE, reactlog_disable(warn = "yes")) + expect_snapshot(reactlog_disable()) + expect_silent(reactlog_disable(warn = FALSE)) +}) + +test_that("bad log versions are explained by the function the user called", { + expect_snapshot(error = TRUE, reactlog_render(list())) + expect_snapshot(error = TRUE, reactlog_render(structure(list(), version = "2"))) +}) + +test_that("assert_shiny_version() uses rlang::check_installed()", { + local_mocked_bindings(shiny_version_required = function(...) package_version("999.0.0")) + expect_error(assert_shiny_version(), class = "rlib_error_package_not_found") +}) + +test_that("reactlog_show() and reactlog_widget() check for shiny when `log` is missing", { + local_mocked_bindings( + check_installed = function(pkg, ...) stop("checked ", pkg), + .package = "rlang" + ) + expect_error(reactlog_show(), "checked shiny") + skip_if_not_installed("htmlwidgets") + local_mocked_bindings( + check_installed = function(pkg, ...) if (pkg == "shiny") stop("checked ", pkg), + .package = "rlang" + ) + expect_error(reactlog_widget(), "checked shiny") +})