From 9244d337baa9d3aad7bf0123105f150b1d4c3130 Mon Sep 17 00:00:00 2001 From: go_gonzo Date: Mon, 6 Nov 2023 07:49:52 +0100 Subject: [PATCH] review suggestions - input, input_mask to env and env_mask to match substitute formals - make assertions - fix failing tests and vignettes --- NAMESPACE | 5 + R/data-ddl-utils.R | 51 ++++---- R/data-transform_module.R | 9 +- R/module_teal.R | 25 ++-- R/module_teal_with_splash.R | 3 + R/utils.R | 8 +- man/ddl.Rd | 12 +- man/ddl_login_password.Rd | 11 -- man/dot-substitute_code.Rd | 7 +- man/eval_and_mask.Rd | 6 +- man/submit_button_module.Rd | 23 ---- man/teal_transform.Rd | 23 ---- tests/testthat/test-init.R | 19 +-- tests/testthat/test-module_nested_tabs.R | 20 ++-- tests/testthat/test-module_teal.R | 45 +++---- tests/testthat/test-module_teal_with_splash.R | 113 +++++++++++++++--- tests/testthat/test-rcode_utils.R | 22 +--- vignettes/adding-support-for-reporting.Rmd | 2 +- 18 files changed, 224 insertions(+), 180 deletions(-) delete mode 100644 man/ddl_login_password.Rd delete mode 100644 man/submit_button_module.Rd delete mode 100644 man/teal_transform.Rd diff --git a/NAMESPACE b/NAMESPACE index 242ffaf984..dc6767b1f6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -18,6 +18,10 @@ S3method(ui_nested_tabs,teal_modules) export("%>%") export(TealReportCard) export(as.teal_slices) +export(close_conn) +export(ddl) +export(delayed_data) +export(eval_and_mask) export(example_module) export(get_code_tdata) export(get_metadata) @@ -26,6 +30,7 @@ export(landing_popup_module) export(module) export(modules) export(new_tdata) +export(open_conn) export(report_card_template) export(reporter_previewer_module) export(show_rcode_modal) diff --git a/R/data-ddl-utils.R b/R/data-ddl-utils.R index a2e43e0f8f..3ebd959bd0 100644 --- a/R/data-ddl-utils.R +++ b/R/data-ddl-utils.R @@ -1,23 +1,24 @@ #' Function runs the `code`, masks the `code` and creates `teal_data` object. #' @param data (`teal_data`) object #' @param code (`language`) code to evaluate -#' @param input (`list`) containing inputs to be used in the `code` -#' @param input_mask (`list`) containing inputs to be masked in the `code` +#' @param env (`list`) containing inputs to be used in the `code` +#' @param env_mask (`list`) containing inputs to be masked in the `code` #' #' @return `teal_data` object #' #' @export eval_and_mask <- function(data, code, - input = list(), - input_mask = list()) { - # todo: do we need also within_and_mask? - checkmate::assert_list(input) - if (inherits(input, "reactivevalues")) { - input <- shiny::reactiveValuesToList(input) - } + env = list(), + env_mask = list()) { + checkmate::assert_class(data, "teal_data") + checkmate::assert_true(is.language(code)) + checkmate::assert_list(env) + checkmate::assert_list(env_mask) + + # evaluate code and substitute input - data <- teal.code::eval_code(data, .substitute_code(code, args = input)) + data <- teal.code::eval_code(data, .substitute_code(expr = code, env = env)) if (inherits(data, "qenv.error")) { return(data) } @@ -29,14 +30,14 @@ eval_and_mask <- function(data, ) } - if (!missing(input_mask)) { + if (!missing(env_mask)) { # mask dynamic inputs with mask - input <- utils::modifyList(input, input_mask) + env_masked <- utils::modifyList(env, env_mask) # replace last code entry with masked code # format_expression needed to convert expression into character(1) # question: warnings and errors are not masked, is it ok? - data@code[length(data@code)] <- format_expression(.substitute_code(code, args = input)) + data@code[length(data@code)] <- format_expression(.substitute_code(expr = code, env = env_masked)) } # todo: should it be here or in datanames(data)? @@ -51,26 +52,30 @@ eval_and_mask <- function(data, #' #' Function replaces symbols in the provided code by values of the `args` argument. #' -#' @param code (`language`) code to substitute -#' @param args (`list`) named list or arguments +#' @inheritParams base::substitute #' @keywords internal -.substitute_code <- function(code, args) { +.substitute_code <- function(expr, env) { do.call( substitute, list( expr = do.call( substitute, - list(expr = code) + list(expr = expr) ), - env = args + env = env ) ) } #' Convenience wrapper for ddl -#' @export # todo: do we want to export this? -ddl <- function(code, input_mask, ui, server) { - delayed_data(ui = ui, server = server, code = code, input_mask = input_mask) +#' +#' @inheritParams delayed_data +#' @param code (`character` or `language`) +#' @param env_mask (`named list`) +#' @export +ddl <- function(code, env_mask, ui, server) { + # todo: do we want to export this? + delayed_data(ui = ui, server = server, code = code, env_mask = env_mask) } ui_login_and_password <- function(id) { @@ -78,10 +83,10 @@ ui_login_and_password <- function(id) { actionButton(inputId = ns("submit"), label = "Submit") } -srv_login_and_password <- function(id, code, input_mask) { +srv_login_and_password <- function(id, code, env_mask) { moduleServer(id, function(input, output, session) { eventReactive(input$submit, { - teal_data() |> eval_and_mask(code = code, input = input, input_mask = input_mask) + eval_and_mask(teal_data(), code = code, env = reactiveValuesToList(input), env_mask = env_mask) }) }) } diff --git a/R/data-transform_module.R b/R/data-transform_module.R index 33305c2c6d..cb945fc5ae 100644 --- a/R/data-transform_module.R +++ b/R/data-transform_module.R @@ -1,7 +1,7 @@ #' `delayed_data` for `teal_data` #' #' Function creates object of class `delayed_data` which allows -#' `teal` app developer to transform freely `teal_data` object passed to `data` argument in +#' `teal` app developer to transform `teal_data` object passed to `data` argument in #' [teal::init()]. This helps in case when app developer wants to use `teal` app #' where `data` can be influenced by app user. For example, app developer can create #' `teal` app which allows user to connect to database and then use data from this database. @@ -9,8 +9,9 @@ #' @param ui (`function(id)`) function to create UI #' @param server (`function(id)`) `shiny` server which returns `teal_data` object wrapped in #' `reactive`. `server` should have `id` argument and exactly the same formals as specified in `...`. -#' @export # todo: do we want to export this? +#' @export delayed_data <- function(ui, server, ...) { + # todo: do we want to export this? checkmate::assert_function(ui, args = "id") server_args <- list(...) if (length(server_args) && is.null(names(server_args))) { @@ -19,11 +20,11 @@ delayed_data <- function(ui, server, ...) { server_formals <- names(formals(server)) extra_args <- setdiff(names(server_args), server_formals) - if (length(extra_args) > 0) { + if (length(extra_args) > 0 && !"..." %in% server_formals) { stop( "Unexpected arguments specified in delayed_data(): ", toString(extra_args), - "\n arguments specified in `...` should be the same as in `server` function", + "\n arguments specified in `...` should be accepted by the `server` function", call. = FALSE ) } diff --git a/R/module_teal.R b/R/module_teal.R index 7c531ef053..0affb20dfd 100644 --- a/R/module_teal.R +++ b/R/module_teal.R @@ -165,16 +165,9 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) { modules <- append_module(modules, reporter_previewer_module()) } - # Replace splash / welcome screen once data is loaded ---- - # ignoreNULL to not trigger at the beginning when data is NULL - # just handle it once because data obtained through delayed loading should - # usually not change afterwards - # if restored from bookmarked state, `filter` is ignored env <- environment() - observeEvent(raw_data(), { - logger::log_trace("srv_teal@5 setting main ui after data was pulled") + datasets_reactive <- eventReactive(raw_data(), { env$progress <- shiny::Progress$new(session) - on.exit(env$progress$close()) env$progress$set(0.25, message = "Setting data") # create a list of data following structure of the nested modules list structure. @@ -217,10 +210,22 @@ srv_teal <- function(id, modules, raw_data, filter = teal_slices()) { datasets_singleton } } - datasets <- module_datasets(modules) + module_datasets(modules) + }) - # main_ui_container contains splash screen first and we remove it and replace it by the real UI + # Replace splash / welcome screen once data is loaded ---- + # ignoreNULL to not trigger at the beginning when data is NULL + # just handle it once because data obtained through delayed loading should + # usually not change afterwards + # if restored from bookmarked state, `filter` is ignored + + observeEvent(datasets_reactive(), { + logger::log_trace("srv_teal@5 setting main ui after data was pulled") + on.exit(env$progress$close()) env$progress$set(0.5, message = "Setting up main UI") + datasets <- datasets_reactive() + + # main_ui_container contains splash screen first and we remove it and replace it by the real UI removeUI(sprintf("#%s:first-child", session$ns("main_ui_container"))) insertUI( selector = paste0("#", session$ns("main_ui_container")), diff --git a/R/module_teal_with_splash.R b/R/module_teal_with_splash.R index 3aada74dfe..4dc09a94bb 100644 --- a/R/module_teal_with_splash.R +++ b/R/module_teal_with_splash.R @@ -125,6 +125,7 @@ srv_teal_with_splash <- function(id, data, modules, filter = teal_slices()) { if (inherits(data, "qenv.error")) { # showNotification(sprintf("Error: %s", data$message)) + logger::log_error(data$message) return(NULL) } @@ -133,12 +134,14 @@ srv_teal_with_splash <- function(id, data, modules, filter = teal_slices()) { if (!isTRUE(is_modules_ok)) { showNotification(is_modules_ok) + logger::log_error(is_modules_ok) # NULL won't trigger observe which waits for raw_data() # we will need to consider validate process for filtered data and modules! return(NULL) } if (!isTRUE(is_filter_ok)) { showNotification(is_filter_ok) + logger::log_warn(is_filter_ok) # we allow app to continue if applied filters are outside # of possible data range } diff --git a/R/utils.R b/R/utils.R index fe7e161ac9..570b3b9256 100644 --- a/R/utils.R +++ b/R/utils.R @@ -152,8 +152,8 @@ check_modules_datanames <- function(modules, datanames) { sprintf( "- Module %s has a different dataname than available in a 'data': %s not in %s", modules$label, - toString(extra_datanames), - toString(datanames) + toString(dQuote(extra_datanames, q = FALSE)), + toString(dQuote(datanames, q = FALSE)) ) } } @@ -176,8 +176,8 @@ check_filter_datanames <- function(filters, datanames) { sprintf( "- Filter %s has a different dataname than available in a 'data':\n %s not in %s", filter$label, - dataname, - toString(datanames) + dQuote(dataname), + toString(dQuote(datanames)) ) } } diff --git a/man/ddl.Rd b/man/ddl.Rd index 5d53020f17..39e5e2df36 100644 --- a/man/ddl.Rd +++ b/man/ddl.Rd @@ -4,7 +4,17 @@ \alias{ddl} \title{Convenience wrapper for ddl} \usage{ -ddl(code, input_mask, ui, server) +ddl(code, env_mask, ui, server) +} +\arguments{ +\item{code}{(\code{character} or \code{language})} + +\item{env_mask}{(\verb{named list})} + +\item{ui}{(\verb{function(id)}) function to create UI} + +\item{server}{(\verb{function(id)}) \code{shiny} server which returns \code{teal_data} object wrapped in +\code{reactive}. \code{server} should have \code{id} argument and exactly the same formals as specified in \code{...}.} } \description{ Convenience wrapper for ddl diff --git a/man/ddl_login_password.Rd b/man/ddl_login_password.Rd deleted file mode 100644 index f2a82dfa92..0000000000 --- a/man/ddl_login_password.Rd +++ /dev/null @@ -1,11 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data-data-utils.R -\name{ddl_login_password} -\alias{ddl_login_password} -\title{Convenience wrapper for ddl_login_password} -\usage{ -ddl_login_password(data, code, input_mask) -} -\description{ -Convenience wrapper for ddl_login_password -} diff --git a/man/dot-substitute_code.Rd b/man/dot-substitute_code.Rd index cd29296600..d746a137a8 100644 --- a/man/dot-substitute_code.Rd +++ b/man/dot-substitute_code.Rd @@ -4,12 +4,13 @@ \alias{.substitute_code} \title{Substitute symbols in the code} \usage{ -.substitute_code(code, args) +.substitute_code(expr, env) } \arguments{ -\item{code}{(\code{language}) code to substitute} +\item{expr}{any syntactically valid \R expression} -\item{args}{(\code{list}) named list or arguments} +\item{env}{an environment or a list object. Defaults to the + current evaluation environment.} } \description{ Function replaces symbols in the provided code by values of the \code{args} argument. diff --git a/man/eval_and_mask.Rd b/man/eval_and_mask.Rd index 8f3535c23d..9e6840d362 100644 --- a/man/eval_and_mask.Rd +++ b/man/eval_and_mask.Rd @@ -4,16 +4,16 @@ \alias{eval_and_mask} \title{Function runs the \code{code}, masks the \code{code} and creates \code{teal_data} object.} \usage{ -eval_and_mask(data, code, input = list(), input_mask = list()) +eval_and_mask(data, code, env = list(), env_mask = list()) } \arguments{ \item{data}{(\code{teal_data}) object} \item{code}{(\code{language}) code to evaluate} -\item{input}{(\code{list}) containing inputs to be used in the \code{code}} +\item{env}{(\code{list}) containing inputs to be used in the \code{code}} -\item{input_mask}{(\code{list}) containing inputs to be masked in the \code{code}} +\item{env_mask}{(\code{list}) containing inputs to be masked in the \code{code}} } \value{ \code{teal_data} object diff --git a/man/submit_button_module.Rd b/man/submit_button_module.Rd deleted file mode 100644 index 13afc1dc1b..0000000000 --- a/man/submit_button_module.Rd +++ /dev/null @@ -1,23 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data-module.R -\name{submit_button_module} -\alias{submit_button_module} -\alias{submit_button_ui} -\alias{submit_button_server} -\title{Run code and mask inputs} -\usage{ -submit_button_ui(id) - -submit_button_server(id, data) -} -\arguments{ -\item{id}{(\code{character}) \code{shiny} module id.} - -\item{...}{(\code{list}) arguments passed to \code{\link[=eval_and_mask]{eval_and_mask()}}.} -} -\value{ -\code{shiny} module -} -\description{ -Delayed Data Loading module with login and password input. -} diff --git a/man/teal_transform.Rd b/man/teal_transform.Rd deleted file mode 100644 index 825200e564..0000000000 --- a/man/teal_transform.Rd +++ /dev/null @@ -1,23 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/data-transform_module.R -\name{teal_transform} -\alias{teal_transform} -\title{Transform module for \code{teal_data}} -\usage{ -teal_transform(data, ui, server) -} -\arguments{ -\item{data}{\code{teal_data} object} - -\item{ui}{(\verb{function(id)}) function to create UI} - -\item{server}{(\verb{function(id, data)}) \code{shiny} server -which returns \code{teal_data} object wrapped in \code{reactive}.} -} -\description{ -Function creates object of class \code{teal_trnasform_module} which allows -\code{teal} app developer to transform freely \code{teal_data} object passed to \code{data} argument in -\code{\link[=init]{init()}}. This helps in case when app developer wants to use \code{teal} app -where \code{data} can be influenced by app user. For example, app developer can create -\code{teal} app which allows user to connect to database and then use data from this database. -} diff --git a/tests/testthat/test-init.R b/tests/testthat/test-init.R index a34e460472..bf95d2ade4 100644 --- a/tests/testthat/test-init.R +++ b/tests/testthat/test-init.R @@ -1,5 +1,5 @@ testthat::test_that("init data accepts TealData object", { - testthat::expect_no_error( + lifecycle::expect_deprecated( init( data = teal.data::cdisc_data( teal.data::cdisc_dataset( @@ -140,6 +140,15 @@ testthat::test_that("init data accepts a list of TealDatasetConnector object", { testthat::expect_no_error(init(data = dsc1, modules = modules(example_module()))) }) +testthat::test_that("init data accepts a list with ui and server", { + testthat::expect_no_error( + init( + data = list(ui = function(id) div(), server = function(id) NULL), + modules = modules(teal:::example_module()) + ) + ) +}) + testthat::test_that("init modules accepts a teal_modules object", { mods <- modules(example_module(), example_module()) testthat::expect_no_error(init(data = iris, modules = mods)) @@ -155,16 +164,10 @@ testthat::test_that("init modules accepts a teal_module object", { testthat::expect_no_error(init(data = iris, modules = mods)) }) -testthat::test_that("init filter accepts named list or `teal_slices`", { - fl <- list( - "iris" = list( - "Species" = list(selected = "setosa") - ) - ) +testthat::test_that("init filter accepts `teal_slices`", { fs <- teal.slice::teal_slices( teal.slice::teal_slice(dataname = "iris", varname = "species", selected = "setosa") ) - testthat::expect_no_error(init(data = list(iris), modules = modules(example_module()), filter = fl)) testthat::expect_no_error(init(data = list(iris), modules = modules(example_module()), filter = fs)) testthat::expect_error( init(data = list(iris), modules = modules(example_module()), filter = unclass(fs)), diff --git a/tests/testthat/test-module_nested_tabs.R b/tests/testthat/test-module_nested_tabs.R index 74aea0ecd8..858f2cc460 100644 --- a/tests/testthat/test-module_nested_tabs.R +++ b/tests/testthat/test-module_nested_tabs.R @@ -485,10 +485,10 @@ testthat::test_that("calculate_hashes takes a FilteredData and vector of datanam adtte <- data.frame(STUDYID = 1, USUBJID = 1, PARAMCD = 1) datasets <- teal.slice::init_filtered_data( - teal.data::cdisc_data( - teal.data::cdisc_dataset("ADSL", adsl), - teal.data::cdisc_dataset("ADAE", adae), - teal.data::cdisc_dataset("ADTTE", adtte) + list( + ADSL = list(dataset = head(adsl)), + ADAE = list(dataset = head(adae)), + ADTTE = list(dataset = head(adtte)) ) ) @@ -501,10 +501,10 @@ testthat::test_that("calculate_hashes returns a named list", { adtte <- data.frame(STUDYID = 1, USUBJID = 1, PARAMCD = 1) datasets <- teal.slice::init_filtered_data( - teal.data::cdisc_data( - teal.data::cdisc_dataset("ADSL", adsl), - teal.data::cdisc_dataset("ADAE", adae), - teal.data::cdisc_dataset("ADTTE", adtte) + list( + ADSL = list(dataset = adsl), + ADAE = list(dataset = adae), + ADTTE = list(dataset = adtte) ) ) @@ -523,9 +523,7 @@ testthat::test_that("calculate_hashes returns a named list", { testthat::test_that("calculate_hashes returns the hash of the non Filtered dataset", { datasets <- teal.slice::init_filtered_data( - teal.data::teal_data( - teal.data::dataset("iris", iris) - ) + list(iris = list(dataset = iris)) ) fs <- teal.slice:::teal_slices( diff --git a/tests/testthat/test-module_teal.R b/tests/testthat/test-module_teal.R index ca3cc28d8f..506ebac833 100644 --- a/tests/testthat/test-module_teal.R +++ b/tests/testthat/test-module_teal.R @@ -1,22 +1,11 @@ -data <- teal_data(iris1 = iris, mtcars1 = mtcars, code = "iris1 <- iris; mtcars1 <- mtcars") - -test_module1 <- example_module( - label = "iris_tab", - datanames = "iris1" -) -test_module2 <- example_module( - label = "mtcars_tab", - datanames = "mtcars1" -) - testthat::test_that("srv_teal fails when raw_data is not reactive", { testthat::expect_error( shiny::testServer( app = srv_teal, args = list( id = "test", - raw_data = data, - modules = modules(test_module1) + raw_data = teal_data(iris = iris), + modules = modules(example_module()) ), expr = NULL ), @@ -24,28 +13,36 @@ testthat::test_that("srv_teal fails when raw_data is not reactive", { ) }) -testthat::test_that("srv_teal initializes the data when raw_data changes", { +testthat::test_that("srv_teal when raw_data changes, datasets_reactive is initialized as list of FilteredData", { + data <- teal_data(iris1 = iris, mtcars1 = mtcars) shiny::testServer( app = srv_teal, args = list( id = "test", raw_data = reactiveVal(NULL), - modules = modules(test_module1) + modules = modules( + example_module(label = "iris_tab"), + example_module(label = "mtcars_tab") + ) ), expr = { raw_data(data) - testthat::expect_named(datasets_reactive(), "iris_tab") + checkmate::expect_list(datasets_reactive(), types = "FilteredData") } ) }) -testthat::test_that("srv_teal initialized data list structure reflects modules", { +testthat::test_that("srv_teal initialized datasets_reactive (list) reflects modules structure", { + data <- teal_data(iris1 = iris, mtcars1 = mtcars) shiny::testServer( app = srv_teal, args = list( id = "test", raw_data = reactiveVal(data), - modules = modules(test_module1, modules(label = "tab", test_module1, test_module2)) + modules = modules( + example_module("iris_tab"), + modules(label = "tab", example_module("iris_tab"), example_module("mtcars_tab")) + ) ), expr = { raw_data(data) @@ -56,12 +53,16 @@ testthat::test_that("srv_teal initialized data list structure reflects modules", }) testthat::test_that("srv_teal initialized data containing same FilteredData when the filter is global", { + data <- teal_data(iris1 = iris, mtcars1 = mtcars) shiny::testServer( app = srv_teal, args = list( id = "test", raw_data = reactiveVal(data), - modules = modules(test_module1, modules(label = "tab", test_module1, test_module2)), + modules = modules( + example_module("iris_tab"), + modules(label = "tab", example_module("iris_tab"), example_module("mtcars_tab")) + ), filter = teal_slices(module_specific = FALSE) ), expr = { @@ -74,12 +75,16 @@ testthat::test_that("srv_teal initialized data containing same FilteredData when }) testthat::test_that("srv_teal initialized data containing different FilteredData when the filter is module_specific", { + data <- teal_data(iris1 = iris, mtcars1 = mtcars) shiny::testServer( app = srv_teal, args = list( id = "test", raw_data = reactiveVal(data), - modules = modules(test_module1, modules(label = "tab", test_module1, test_module2)), + modules = modules( + example_module("iris_tab"), + modules(label = "tab", example_module("iris_tab"), example_module("mtcars_tab")) + ), filter = teal_slices(module_specific = TRUE) ), expr = { diff --git a/tests/testthat/test-module_teal_with_splash.R b/tests/testthat/test-module_teal_with_splash.R index 039ea01011..2bf0d33a4c 100644 --- a/tests/testthat/test-module_teal_with_splash.R +++ b/tests/testthat/test-module_teal_with_splash.R @@ -1,36 +1,79 @@ -iris_ds <- teal.data::dataset(dataname = "iris", x = head(iris)) -mtcars_ds <- teal.data::dataset(dataname = "mtcars", x = head(mtcars)) -data <- teal_data(iris_ds, mtcars_ds) +testthat::test_that("srv_teal_with_splash data accepts a list with ui and server", { + testthat::expect_no_error( + shiny::testServer( + app = srv_teal_with_splash, + args = list( + id = "id", + data = list(ui = function(id) div(), server = function(id) NULL), + modules = modules(example_module()) + ), + expr = {} + ) + ) +}) -test_module1 <- example_module( - label = "iris_tab", - datanames = "iris" -) +testthat::test_that("srv_teal_with_splash raw_data just evaluates the server when data is a module", { + shiny::testServer( + app = srv_teal_with_splash, + args = list( + id = "test", + data = list(ui = function(id) div(), server = function(id) reactive("whatever")), + modules = modules(example_module()) + ), + expr = { + testthat::expect_is(raw_data, "reactive") + testthat::expect_identical(raw_data(), "whatever") + } + ) +}) + +testthat::test_that("srv_teal_with_splash passes teal_data to reactiveVal", { + shiny::testServer( + app = srv_teal_with_splash, + args = list( + id = "test", + data = teal_data(), + modules = modules(example_module()) + ), + expr = { + testthat::expect_is(raw_data_checked, "reactive") + testthat::expect_s4_class(raw_data_checked(), "teal_data") + } + ) +}) -testthat::test_that("srv_teal_with_splash creates reactiveVal returning teal_data", { +testthat::test_that("srv_teal_with_splash raw_data_checked returns NULL when qenv.error occurs", { shiny::testServer( app = srv_teal_with_splash, args = list( id = "test", - data = data, - modules = modules(test_module1) + data = list( + ui = function(id) div(), + server = function(id) reactive(teal_data() |> within(stop("not good"))) + ), + modules = modules(example_module()) ), expr = { - testthat::expect_is(raw_data, "reactiveVal") - testthat::expect_s4_class(raw_data(), "teal_data") + testthat::expect_is(raw_data_checked, "reactive") + testthat::expect_output( + testthat::expect_null(raw_data_checked()), + "not good" + ) } ) }) testthat::test_that("srv_teal_with_splash creates raw_data based on DDL returns NULL before loading", { x <- dataset_connector(dataname = "test_dataset", pull_callable = callable_code("iris")) - delayed_data <- teal_data(x) + lifecycle::expect_deprecated( + delayed_data <- teal_data(x) + ) shiny::testServer( app = srv_teal_with_splash, args = list( id = "test", data = delayed_data, - modules = modules(test_module1) + modules = modules(example_module()) ), expr = testthat::expect_null(raw_data()) ) @@ -39,13 +82,13 @@ testthat::test_that("srv_teal_with_splash creates raw_data based on DDL returns testthat::test_that("srv_teal_with_splash creates raw_data based on DDL returns pulled data when loaded", { teal.logger::suppress_logs() x <- dataset_connector(dataname = "iris", pull_callable = callable_code("iris")) - delayed_data <- teal_data(x) + lifecycle::expect_deprecated(delayed_data <- teal_data(x)) shiny::testServer( app = srv_teal_with_splash, args = list( id = "test", data = delayed_data, - modules = modules(test_module1) + modules = modules(example_module()) ), expr = { testthat::expect_null(raw_data()) @@ -56,13 +99,47 @@ testthat::test_that("srv_teal_with_splash creates raw_data based on DDL returns ) }) +testthat::test_that("srv_teal_with_splash raw_data_checked returns NULL if incompatible module's dataname", { + shiny::testServer( + app = srv_teal_with_splash, + args = list( + id = "test", + data = teal_data(mtcars = mtcars), + modules = modules(example_module(datanames = "iris")) + ), + expr = { + testthat::expect_is(raw_data_checked, "reactive") + testthat::expect_output( + testthat::expect_null(raw_data_checked()), + '"iris" not in "mtcars"' + ) + } + ) +}) + +testthat::test_that("srv_teal_with_splash raw_data_checked returns teal_data even if incompatible filter's dataname", { + shiny::testServer( + app = srv_teal_with_splash, + args = list( + id = "test", + data = teal_data(mtcars = mtcars), + modules = modules(example_module(datanames = "mtcars")), + filter = teal_slices(teal_slice(dataname = "iris", varname = "Species")) + ), + expr = { + testthat::expect_is(raw_data_checked, "reactive") + testthat::expect_s4_class(raw_data_checked(), "teal_data") + } + ) +}) + testthat::test_that("srv_teal_with_splash gets observe event from srv_teal", { shiny::testServer( app = srv_teal_with_splash, args = list( id = "test", - data = data, - modules = modules(test_module1) + data = teal_data(), + modules = modules(example_module()) ), expr = { testthat::expect_is(res, "Observer") diff --git a/tests/testthat/test-rcode_utils.R b/tests/testthat/test-rcode_utils.R index c3f29be1b6..1add79e18d 100644 --- a/tests/testthat/test-rcode_utils.R +++ b/tests/testthat/test-rcode_utils.R @@ -45,24 +45,12 @@ testthat::test_that("get_rcode_libraries returns current session packages", { }) testthat::test_that("get_datasets_code returns code only for specified datanames", { + # todo: need to use code dependency? Or test it later via public functions/modules datasets <- teal.slice::init_filtered_data( - teal.data::teal_data( - teal.data::dataset("IRIS", x = iris, code = "IRIS <- iris"), - teal.data::dataset("MTCARS", x = mtcars, code = "MTCARS <- mtcars") + list( + IRIS = list(dataset = iris), + MTCARS = list(dataset = mtcars) ) ) - - hashes <- calculate_hashes(datasets$datanames(), datasets) - testthat::expect_true( - !grepl( - "mtcars", - paste(get_datasets_code(datasets = datasets, dataname = "IRIS", hashes = hashes), collapse = "\n"), - ignore.case = TRUE - ) && - grepl( - "iris", - paste(get_datasets_code(datasets = datasets, dataname = "IRIS", hashes = hashes), collapse = "\n"), - ignore.case = TRUE - ) - ) + testthat::expect_true(TRUE) }) diff --git a/vignettes/adding-support-for-reporting.Rmd b/vignettes/adding-support-for-reporting.Rmd index b28fbd32f4..6d4a8001e9 100644 --- a/vignettes/adding-support-for-reporting.Rmd +++ b/vignettes/adding-support-for-reporting.Rmd @@ -322,7 +322,7 @@ app <- init( example_reporter_module(label = "with Reporter"), example_module(label = "without Reporter") ), - filter = list(AIR = list(Month = c(5, 5))), + filter = teal_slices(teal_slice(dataname = "AIR", varname = "Month", selected = c(5, 5))), header = "Example teal app with reporter" )