From 9d88f72b1d8e0f341ef1ef98ae7e057f6913bdc1 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Thu, 11 Sep 2025 12:17:24 +0200 Subject: [PATCH 01/33] init POC MDR --- DESCRIPTION | 1 + NAMESPACE | 3 + R/after.R | 45 ++++++++- R/module_nested_tabs.R | 100 ++++++++++++-------- R/module_teal.R | 1 - R/module_transform_data.R | 42 +++++---- R/standard_layout.R | 176 +++++++++++++++++++++++++++++++++++ R/zzz.R | 1 + man/badge_dropdown.Rd | 21 +++++ man/module_transform_data.Rd | 6 +- 10 files changed, 328 insertions(+), 68 deletions(-) create mode 100644 R/standard_layout.R create mode 100644 man/badge_dropdown.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 44bddf74eb..32315cf115 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -122,6 +122,7 @@ Collate: 'module_transform_data.R' 'reporter_previewer_module.R' 'show_rcode_modal.R' + 'standard_layout.R' 'tdata.R' 'teal.R' 'teal_data_module.R' diff --git a/NAMESPACE b/NAMESPACE index 837845c8bd..a8523cdf8b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -18,6 +18,7 @@ export(add_landing_modal) export(after) export(as.teal_slices) export(as_tdata) +export(badge_dropdown) export(build_app_title) export(disable_report) export(example_module) @@ -39,8 +40,10 @@ export(srv_session_info) export(srv_teal) export(srv_teal_with_splash) export(srv_transform_teal_data) +export(standard_layout2) export(tdata2env) export(teal_data_module) +export(teal_nav_item) export(teal_slices) export(teal_transform_module) export(ui_session_info) diff --git a/R/after.R b/R/after.R index 0a9b20d511..7344894052 100644 --- a/R/after.R +++ b/R/after.R @@ -23,8 +23,8 @@ after <- function(x, additional_args <- list(...) new_x <- x # because overwriting x$ui/server will cause infinite recursion - new_x$ui <- .after_ui(x$ui, ui, additional_args) - new_x$server <- .after_server(x$server, server, additional_args) + new_x$ui <- .after_ui(x = x$ui, y = ui, additional_args = additional_args) + new_x$server <- .after_server(x = x$server, y = server, additional_args = additional_args) new_x } @@ -80,8 +80,45 @@ after <- function(x, list(id = "wrapper", input = input, output = output, session = session) ) reactive({ - req(original_out_r()) - wrapper_args$data <- original_out() + wrapper_args$data <- req(original_out_r()) + do.call(`_y`, wrapper_args[names(formals(`_y`))], quote = TRUE) + }) + }) + } + formals(new_x) <- formals(x) + new_x +} + +.before_server <- function(x, y, additional_args) { + # add `_`-prefix to make sure objects are not masked in the wrapper functions + `_x` <- x # nolint: object_name. + `_y` <- y # nolint: object_name. + new_x <- function(id, ...) { + original_args <- as.list(environment()) + original_args$id <- "wrapped" + if ("..." %in% names(formals(`_x`))) { + original_args <- c(original_args, list(...)) + } + moduleServer(id, function(input, output, session) { + original_out <- if (all(c("input", "output", "session") %in% names(formals(`_x`)))) { + original_args$module <- `_x` + do.call(shiny::callModule, args = original_args) + } else { + do.call(`_x`, original_args) + } + original_out_r <- reactive( + if (is.reactive(original_out)) { + original_out() + } else { + original_out + } + ) + wrapper_args <- utils::modifyList( + additional_args, + list(id = "wrapper", input = input, output = output, session = session) + ) + reactive({ + wrapper_args$data <- req(original_out_r()) do.call(`_y`, wrapper_args[names(formals(`_y`))], quote = TRUE) }) }) diff --git a/R/module_nested_tabs.R b/R/module_nested_tabs.R index f81d250c49..13d8732800 100644 --- a/R/module_nested_tabs.R +++ b/R/module_nested_tabs.R @@ -188,7 +188,59 @@ srv_teal_module <- function(id, .ui_teal_module.teal_module <- function(id, modules, active_module_id) { ns <- NS(id) args <- c(list(id = ns("module")), modules$ui_args) - ui_teal <- tags$div( + + module_ui <- do.call(what = modules$ui, args = args, quote = TRUE) + + is_transform_relocated <- !identical(getOption("teal.transform.location"), ".teal-sidebar") && + length(htmltools::tagQuery(module_ui)$find(getOption("teal.transform.location"))$selectedTags()) + + container_id <- ns("wrapper") + module_id <- modules$path + + data_summary_ui <- tags$div( + class = "teal-active-data-summary-panel", + bslib::accordion( + id = ns("data_summary_accordion"), + bslib::accordion_panel( + "Active Data Summary", + tags$div( + class = "teal-active-data-summary", + ui_data_summary(ns("data_summary")) + ) + ) + ) + ) + + transformators_ui <- if (length(modules$transformators) > 0 && !isTRUE(attr(modules$transformators, "custom_ui"))) { + ui_transform_teal_data( + ns("data_transform"), + transformators = modules$transformators, + container = if (is_transform_relocated) "div" else "accordion" + ) + } + + if (is_transform_relocated) { + module_ui <- htmltools::tagInsertChildren( + tag = module_ui, + transformators_ui, + .cssSelector = getOption("teal.transform.location"), + after = 0 + ) + transformators_wrapper_ui <- NULL + } else { + transformators_wrapper_ui <- tags$div( + tags$br(), + tags$div( + class = "teal-transform-panel", + bslib::accordion( + id = ns("data_transform_accordion"), + bslib::accordion_panel("Transform Data", transformators_ui) + ) + ) + ) + } + + module_wrapper_ui <- tags$div( shinyjs::hidden( tags$div( id = ns("transform_failure_info"), @@ -205,11 +257,9 @@ srv_teal_module <- function(id, class = "teal_validated", ui_check_module_datanames(ns("validate_datanames")) ), - do.call(what = modules$ui, args = args, quote = TRUE) + module_ui ) ) - container_id <- ns("wrapper") - module_id <- modules$path link <- tags$li( tags$a( @@ -238,45 +288,13 @@ srv_teal_module <- function(id, position = getOption("teal.sidebar.position", "left"), width = getOption("teal.sidebar.width", 250), tags$div( - tags$div( - class = "teal-active-data-summary-panel", - bslib::accordion( - id = ns("data_summary_accordion"), - bslib::accordion_panel( - "Active Data Summary", - tags$div( - class = "teal-active-data-summary", - ui_data_summary(ns("data_summary")) - ) - ) - ) - ), + data_summary_ui, tags$br(), - tags$div( - class = "teal-filter-panel", - ui_filter_data(ns("filter_panel")) - ), - if (length(modules$transformators) > 0 && !isTRUE(attr(modules$transformators, "custom_ui"))) { - tags$div( - tags$br(), - tags$div( - class = "teal-transform-panel", - bslib::accordion( - id = ns("data_transform_accordion"), - bslib::accordion_panel( - "Transform Data", - ui_transform_teal_data( - ns("data_transform"), - transformators = modules$transformators - ) - ) - ) - ) - ) - } + tags$div(class = "teal-filter-panel", ui_filter_data(ns("filter_panel"))), + transformators_wrapper_ui ) ), - ui_teal + module_wrapper_ui ), div( id = ns("sidebar_toggle_buttons"), @@ -291,7 +309,7 @@ srv_teal_module <- function(id, ns("data_filters_toggle"), icon("fas fa-filter") ), - if (length(modules$transformators) > 0) { + if (!is_transform_relocated && length(modules$transformators) > 0) { actionButton( class = "data-transforms-toggle btn-outline-primary", ns("data_transforms_toggle"), diff --git a/R/module_teal.R b/R/module_teal.R index 90a1b9a0ad..75600c9f72 100644 --- a/R/module_teal.R +++ b/R/module_teal.R @@ -69,7 +69,6 @@ ui_teal <- function(id, modules) { ) navbar <- ui_teal_module(id = ns("teal_modules"), modules = modules) - module_items <- ui_teal_module(id = ns("teal_modules"), modules = modules) nav_elements <- list( withr::with_options(reporter_opts, { # for backwards compatibility of the report_previewer_module$server_args shinyjs::hidden( diff --git a/R/module_transform_data.R b/R/module_transform_data.R index 26b49f0b47..2e4c604fe6 100644 --- a/R/module_transform_data.R +++ b/R/module_transform_data.R @@ -14,8 +14,9 @@ NULL #' @export #' @rdname module_transform_data -ui_transform_teal_data <- function(id, transformators, class = "well") { +ui_transform_teal_data <- function(id, transformators, container = c("accordion", "div")) { checkmate::assert_string(id) + container <- match.arg(container) if (length(transformators) == 0L) { return(NULL) } @@ -33,29 +34,32 @@ ui_transform_teal_data <- function(id, transformators, class = "well") { data_mod <- transformators[[name]] transform_wrapper_id <- ns(sprintf("wrapper_%s", name)) - display_fun <- if (is.null(data_mod$ui)) shinyjs::hidden else function(x) x + transformator_ui <- tags$div( + id = transform_wrapper_id, + if (is.null(data_mod$ui)) { + return(NULL) + } else { + data_mod$ui(id = ns("transform")) + }, + div( + id = ns("validate_messages"), + class = "teal_validated", + uiOutput(ns("error_wrapper")) + ) + ) - display_fun( + transformator_wrapper <- if (container == "accordion") { bslib::accordion( bslib::accordion_panel( - attr(data_mod, "label"), + title = attr(data_mod, "label"), icon = bsicons::bs_icon("palette-fill"), - tags$div( - id = transform_wrapper_id, - if (is.null(data_mod$ui)) { - return(NULL) - } else { - data_mod$ui(id = ns("transform")) - }, - div( - id = ns("validate_messages"), - class = "teal_validated", - uiOutput(ns("error_wrapper")) - ) - ) + transformator_ui ) ) - ) + } else { + teal::teal_nav_item(transformator_ui, label = tags$strong(attr(data_mod, "label"))) + } + if (is.null(data_mod$ui)) shinyjs::hidden(transformator_wrapper) else transformator_wrapper } ) } @@ -83,7 +87,7 @@ srv_transform_teal_data <- function(id, data, transformators, modules = NULL, is data_out <- reactiveVal() .call_once_when(inherits(data_previous(), "teal_data"), { - logger::log_debug("srv_teal_transform_teal_data@2 triggering a transform module call for { name }.") + logger::log_debug("srv_transform_teal_data@2 triggering a transform module call for { name }.") data_unhandled <- transformators[[name]]$server("transform", data = data_previous) data_handled <- reactive(tryCatch(data_unhandled(), error = function(e) e)) diff --git a/R/standard_layout.R b/R/standard_layout.R new file mode 100644 index 0000000000..e5da4447b6 --- /dev/null +++ b/R/standard_layout.R @@ -0,0 +1,176 @@ +#' @rdname standard_layout +#' @export +standard_layout2 <- function(output, + encoding = NULL, + forms = NULL, + pre_output = NULL, + post_output = NULL) { + checkmate::assert_multi_class(output, c("shiny.tag", "shiny.tag.list", "html")) + checkmate::assert_multi_class(encoding, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) + + # if encoding=NULL then forms is placed below output + + tag_output <- tags$div( + class = "teal standard-layout", + tags$div(class = "standard-layout-pre-output", pre_output), + tags$div(class = "standard-layout-output", output), + tags$div(class = "standard-layout-post-output", post_output) + ) + + + output_panel <- tags$div(output, class = "teal standard-layout output-panel") + out <- if (!is.null(encoding)) { + tags$div( + tags$nav( + class = "navbar navbar-expand-lg navbar-light bg-light", + tags$div( + class = "navbar-nav d-flex flex-row flex-nowrap teal standard-layout encoding-panel", + style = "width: 100%;", + encoding + ) + ), + output_panel + ) + } else { + output_panel + } + + + bslib::page_fluid(out, class = "teal standard-layout-wrapper") +} + +#' @export +teal_nav_item <- function(label = NULL, ...) { + checkmate::assert_list(list(...), c("shiny.tag", "shiny.tag.list", "html", "character")) + checkmate::assert_multi_class(label, c("shiny.tag", "shiny.tag.list", "html", "character"), null.ok = TRUE) + tags$div( + class = "nav-item", + style = "min-width: 150px;", + label, + tagList(list(...)) + ) +} + +#' Dropdown badge +#' +#' Dropdown button in a form of a badge with `bg-primary` as default style +#' Clicking badge shows a dropdown containing any `HTML` element. Folded dropdown +#' doesn't trigger display output which means that items rendered using `render*` +#' will be recomputed only when dropdown is show. +#' +#' @param id (`character(1)`) shiny module's id +#' @param label (`shiny.tag`) Label displayed on a badge. +#' @param ... (`shiny.tag`) Content of a dropdown. +#' @export +badge_dropdown <- function(id, label, content) { + ns <- shiny::NS(id) + htmltools::tagList( + htmltools::tags$style(" + .choices-selected-badge-dropdown:has(~ div .shiny-validation-message) { + border-color: red !important; + } + .choices-selected-badge-dropdown { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + border-radius: 0.375rem; + line-height: 1.2; + min-width: auto; + } + .choices-selected-badge-dropdown:after { + margin-left: 0.25rem; + vertical-align: 0.1em; + } + "), + htmltools::tags$div( + htmltools::tags$span( + id = ns("summary_badge"), + class = "badge bg-primary rounded-pill choices-selected-badge-dropdown", + label, + bsicons::bs_icon("caret-down-fill"), + onclick = sprintf( + " + var container = document.getElementById('%s'); + var summary = document.getElementById('%s'); + + if(container.style.display === 'none' || container.style.display === '') { + container.style.display = 'block'; + $(container).trigger('shown'); + Shiny.bindAll(container); + + + // Add click outside handler + setTimeout(function() { + function handleClickOutside(event) { + if (!container.contains(event.target) && !summary.contains(event.target)) { + container.style.display = 'none'; + $(container).trigger('hidden'); + document.removeEventListener('click', handleClickOutside); + } + } + document.addEventListener('click', handleClickOutside); + }, 10); + } else { + container.style.display = 'none'; + $(container).trigger('hidden'); + } + ", + ns("inputs_container"), + ns("summary_badge") + ) + ), + htmltools::tags$div( + content, + id = ns("inputs_container"), + style = "display: none; position: absolute; background: white; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 10px; z-index: 1000; min-width: 200px;", + ) + ) + ) +} + +# this one uses bootstrap but doesn't work for some reason +.badge_dropdown <- function(id, label, ...) { + checkmate::assert_list(list(...), c("shiny.tag", "shiny.tag.list", "html", "character")) + ns <- shiny::NS(id) + + btn_id <- ns("dropdown_menu") + + htmltools::tagList( + htmltools::tags$style(" + .choices-selected-badge-dropdown:has(~ div .shiny-validation-message) { + border-color: red !important; + } + .choices-selected-badge-dropdown .btn { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + border-radius: 0.375rem; + line-height: 1.2; + min-width: auto; + } + .choices-selected-badge-dropdown .btn::after { + margin-left: 0.25rem; + vertical-align: 0.1em; + } + "), + tags$div( + class = "dropdown choices-selected-badge-dropdown", + tags$a( + id = btn_id, + class = "btn btn-primary btn-sm dropdown-toggle", + href = "#", + role = "button", + `data-bs-toggle` = "dropdown", + `data-toggle` = "dropdown", + `aria-haspopup` = TRUE, + `aria-expanded` = FALSE, + label + ), + tags$div( + class = "dropdown-menu", + `aria-labelledby` = btn_id, + rlang::list2(...) + ) + ) + ) +} diff --git a/R/zzz.R b/R/zzz.R index ba69dc6bac..d06db045dc 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -6,6 +6,7 @@ teal.lockfile.mode = "auto", shiny.sanitize.errors = FALSE, teal.sidebar.position = "left", + teal.transform.location = ".teal-sidebar", # alternatively .standard-layout.encoding-panel teal.sidebar.width = 250, teal.reporter.nav_buttons = c("preview", "download", "load", "reset") ) diff --git a/man/badge_dropdown.Rd b/man/badge_dropdown.Rd new file mode 100644 index 0000000000..4214eedd90 --- /dev/null +++ b/man/badge_dropdown.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/standard_layout.R +\name{badge_dropdown} +\alias{badge_dropdown} +\title{Dropdown badge} +\usage{ +badge_dropdown(id, label, content) +} +\arguments{ +\item{id}{(\code{character(1)}) shiny module's id} + +\item{label}{(\code{shiny.tag}) Label displayed on a badge.} + +\item{...}{(\code{shiny.tag}) Content of a dropdown.} +} +\description{ +Dropdown button in a form of a badge with \code{bg-primary} as default style +Clicking badge shows a dropdown containing any \code{HTML} element. Folded dropdown +doesn't trigger display output which means that items rendered using \verb{render*} +will be recomputed only when dropdown is show. +} diff --git a/man/module_transform_data.Rd b/man/module_transform_data.Rd index a0760e10d3..23f3f84c80 100644 --- a/man/module_transform_data.Rd +++ b/man/module_transform_data.Rd @@ -6,7 +6,7 @@ \alias{srv_transform_teal_data} \title{Module to transform \code{reactive} \code{teal_data}} \usage{ -ui_transform_teal_data(id, transformators, class = "well") +ui_transform_teal_data(id, transformators, container = c("accordion", "div")) srv_transform_teal_data( id, @@ -22,8 +22,6 @@ srv_transform_teal_data( \item{transformators}{(\code{list} of \code{teal_transform_module}) that will be applied to transform module's data input. To learn more check \code{vignette("transform-input-data", package = "teal")}.} -\item{class}{(character(1)) CSS class to be added in the \code{div} wrapper tag.} - \item{data}{(\code{teal_data}, \code{teal_data_module}, or \code{reactive} returning \code{teal_data}) The data which application will depend on.} @@ -32,6 +30,8 @@ The data which application will depend on.} \item{is_transform_failed}{(\code{reactiveValues}) contains \code{logical} flags named after each transformator. Help to determine if any previous transformator failed, so that following transformators can be disabled and display a generic failure message.} + +\item{class}{(character(1)) CSS class to be added in the \code{div} wrapper tag.} } \value{ \code{reactive} \code{teal_data} From bf04a8482468cc549935d80112071a91b68777eb Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Mon, 15 Sep 2025 13:59:49 +0000 Subject: [PATCH 02/33] fix for front_page or other modules with datanames = NULL --- R/module_nested_tabs.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/module_nested_tabs.R b/R/module_nested_tabs.R index 13d8732800..23a2d0e6e7 100644 --- a/R/module_nested_tabs.R +++ b/R/module_nested_tabs.R @@ -190,7 +190,6 @@ srv_teal_module <- function(id, args <- c(list(id = ns("module")), modules$ui_args) module_ui <- do.call(what = modules$ui, args = args, quote = TRUE) - is_transform_relocated <- !identical(getOption("teal.transform.location"), ".teal-sidebar") && length(htmltools::tagQuery(module_ui)$find(getOption("teal.transform.location"))$selectedTags()) @@ -332,7 +331,7 @@ srv_teal_module <- function(id, ) ) } else { - ui_teal + module_ui } ) ) From 8cf261afe7c4d3877b6b1677e84df9b33145a6b2 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 23 Sep 2025 13:20:08 +0200 Subject: [PATCH 03/33] badge dropdown styling - same size, scrollable --- R/standard_layout.R | 51 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/R/standard_layout.R b/R/standard_layout.R index e5da4447b6..e7981420f7 100644 --- a/R/standard_layout.R +++ b/R/standard_layout.R @@ -68,27 +68,54 @@ badge_dropdown <- function(id, label, content) { ns <- shiny::NS(id) htmltools::tagList( htmltools::tags$style(" - .choices-selected-badge-dropdown:has(~ div .shiny-validation-message) { + .badge-dropdown:has(~ div .shiny-validation-message) { border-color: red !important; } - .choices-selected-badge-dropdown { + .badge-dropdown { padding: 0.25rem 0.5rem; font-size: 0.75rem; border-radius: 0.375rem; line-height: 1.2; min-width: auto; + width: 130px; + overflow-x: auto; + white-space: nowrap; + position: relative; + padding-right: .5rem; } - .choices-selected-badge-dropdown:after { - margin-left: 0.25rem; - vertical-align: 0.1em; + .badge-dropdown-label { + display: block; + max-width: calc(100% - .5rem); + overflow-x: auto; + white-space: nowrap; + scrollbar-width: none; + -ms-overflow-style: none; + text-align: left; + } + .badge-dropdown-label::-webkit-scrollbar { + display: none; + } + .badge-dropdown-icon { + position: absolute; + top: 50%; + right: 0.5rem; + transform: translateY(-50%); + background: inherit; + z-index: 1; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s ease; + } + .badge-dropdown:hover .badge-dropdown-icon { + opacity: 1; } "), htmltools::tags$div( htmltools::tags$span( id = ns("summary_badge"), - class = "badge bg-primary rounded-pill choices-selected-badge-dropdown", - label, - bsicons::bs_icon("caret-down-fill"), + class = "badge bg-primary rounded-pill badge-dropdown", + tags$span(class = "badge-dropdown-label", label), + tags$span(class = "badge-dropdown-icon", bsicons::bs_icon("caret-down-fill")), onclick = sprintf( " var container = document.getElementById('%s'); @@ -138,23 +165,23 @@ badge_dropdown <- function(id, label, content) { htmltools::tagList( htmltools::tags$style(" - .choices-selected-badge-dropdown:has(~ div .shiny-validation-message) { + .badge-dropdown:has(~ div .shiny-validation-message) { border-color: red !important; } - .choices-selected-badge-dropdown .btn { + .badge-dropdown .btn { padding: 0.25rem 0.5rem; font-size: 0.75rem; border-radius: 0.375rem; line-height: 1.2; min-width: auto; } - .choices-selected-badge-dropdown .btn::after { + .badge-dropdown .btn::after { margin-left: 0.25rem; vertical-align: 0.1em; } "), tags$div( - class = "dropdown choices-selected-badge-dropdown", + class = "dropdown badge-dropdown", tags$a( id = btn_id, class = "btn btn-primary btn-sm dropdown-toggle", From 3339e53b715c916c6993c086c3969e243db83563 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 23 Sep 2025 15:51:33 +0200 Subject: [PATCH 04/33] dropdown active for shiny --- R/standard_layout.R | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/R/standard_layout.R b/R/standard_layout.R index e7981420f7..8974cead1f 100644 --- a/R/standard_layout.R +++ b/R/standard_layout.R @@ -121,8 +121,10 @@ badge_dropdown <- function(id, label, content) { var container = document.getElementById('%s'); var summary = document.getElementById('%s'); - if(container.style.display === 'none' || container.style.display === '') { - container.style.display = 'block'; + if(container.style.visibility === 'hidden' || container.style.visibility === '') { + container.style.visibility = 'visible'; + container.style.opacity = '1'; + container.style.pointerEvents = 'auto'; $(container).trigger('shown'); Shiny.bindAll(container); @@ -131,7 +133,9 @@ badge_dropdown <- function(id, label, content) { setTimeout(function() { function handleClickOutside(event) { if (!container.contains(event.target) && !summary.contains(event.target)) { - container.style.display = 'none'; + container.style.visibility = 'hidden'; + container.style.opacity = '0'; + container.style.pointerEvents = 'none'; $(container).trigger('hidden'); document.removeEventListener('click', handleClickOutside); } @@ -139,7 +143,9 @@ badge_dropdown <- function(id, label, content) { document.addEventListener('click', handleClickOutside); }, 10); } else { - container.style.display = 'none'; + container.style.visibility = 'hidden'; + container.style.opacity = '0'; + container.style.pointerEvents = 'none'; $(container).trigger('hidden'); } ", @@ -150,7 +156,7 @@ badge_dropdown <- function(id, label, content) { htmltools::tags$div( content, id = ns("inputs_container"), - style = "display: none; position: absolute; background: white; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 10px; z-index: 1000; min-width: 200px;", + style = "visibility: hidden; opacity: 0; pointer-events: none; position: absolute; background: white; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 10px; z-index: 1000; min-width: 200px; transition: opacity 0.2s ease;", ) ) ) From 1feb3eb853286fe3717e513213f4f4a3b46fe094 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Wed, 24 Sep 2025 13:59:01 +0200 Subject: [PATCH 05/33] POC 1 --- DESCRIPTION | 1 + NAMESPACE | 1 + R/module_teal.R | 3 ++- R/standard_layout.R | 6 +++++- R/teal.transform_wrappers.R | 34 ++++++++++++++++++++++++++++++ R/validate_inputs.R | 42 +++++++++++++++++++++++++++++++++++++ inst/css/validation.css | 6 +++--- inst/js/extendShinyJs.js | 22 ------------------- inst/js/input-validator.js | 29 +++++++++++++++++++++++++ man/validate_input.Rd | 30 ++++++++++++++++++++++++++ 10 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 R/teal.transform_wrappers.R delete mode 100644 inst/js/extendShinyJs.js create mode 100644 inst/js/input-validator.js create mode 100644 man/validate_input.Rd diff --git a/DESCRIPTION b/DESCRIPTION index bc9f1ece1c..c235751fd8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -125,6 +125,7 @@ Collate: 'standard_layout.R' 'tdata.R' 'teal.R' + 'teal.transform_wrappers.R' 'teal_data_module.R' 'teal_data_module-eval_code.R' 'teal_data_module-within.R' diff --git a/NAMESPACE b/NAMESPACE index a8523cdf8b..e787f613f1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -45,6 +45,7 @@ export(tdata2env) export(teal_data_module) export(teal_nav_item) export(teal_slices) +export(teal_transform_filter) export(teal_transform_module) export(ui_session_info) export(ui_teal) diff --git a/R/module_teal.R b/R/module_teal.R index 75600c9f72..565f93c719 100644 --- a/R/module_teal.R +++ b/R/module_teal.R @@ -108,7 +108,8 @@ ui_teal <- function(id, modules) { theme = get_teal_bs_theme(), include_teal_css_js(), shinyjs::useShinyjs(), - shiny::includeScript(system.file("js/extendShinyJs.js", package = "teal.reporter")), + shiny::singleton(shiny::includeScript(system.file("js/extendShinyJs.js", package = "teal.reporter"))), + shiny::singleton(shiny::includeScript(system.file("js/input-validator.js", package = "teal"))), shiny_busy_message_panel, tags$div(id = ns("tabpanel_wrapper"), class = "teal-body", navbar), tags$hr(style = "margin: 1rem 0 0.5rem 0;") diff --git a/R/standard_layout.R b/R/standard_layout.R index 8974cead1f..118ce4e2fe 100644 --- a/R/standard_layout.R +++ b/R/standard_layout.R @@ -68,7 +68,10 @@ badge_dropdown <- function(id, label, content) { ns <- shiny::NS(id) htmltools::tagList( htmltools::tags$style(" - .badge-dropdown:has(~ div .shiny-validation-message) { + .badge-dropdown-wrapper:has(.shiny-validation-message), + .badge-dropdown-wrapper:has(.shiny-output-error), + .badge-dropdown-wrapper:has(* .shiny-validation-message), + .badge-dropdown-wrapper:has(* .shiny-output-error) { border-color: red !important; } .badge-dropdown { @@ -111,6 +114,7 @@ badge_dropdown <- function(id, label, content) { } "), htmltools::tags$div( + class = "badge-dropdown-wrapper", htmltools::tags$span( id = ns("summary_badge"), class = "badge bg-primary rounded-pill badge-dropdown", diff --git a/R/teal.transform_wrappers.R b/R/teal.transform_wrappers.R new file mode 100644 index 0000000000..49400977ab --- /dev/null +++ b/R/teal.transform_wrappers.R @@ -0,0 +1,34 @@ +#' @export +teal_transform_filter <- function(x, label = "Filter") { + checkmate::assert_class(x, "picks") + checkmate::assert_true("values" %in% names(x)) + + teal_transform_module( + label = label, + ui <- function(id) { + ns <- NS(id) + teal.transform::module_input_ui(ns("transformer"), spec = x) + }, + server <- function(id, data) { + moduleServer(id, function(input, output, session) { + selector <- teal.transform::module_input_srv("transformer", spec = x, data = data) + reactive({ + req(data(), selector()) + teal.code::eval_code(data(), .make_filter_call(selector())) + }) + }) + } + ) +} + +.make_filter_call <- function(x) { + checkmate::assert_class(x, "picks") + substitute( + dataname <- dplyr::filter(dataname, varname %in% values), + list( + dataname = str2lang(x$datasets$selected), + varname = str2lang(x$variables$selected), + values = x$values$selected + ) + ) +} diff --git a/R/validate_inputs.R b/R/validate_inputs.R index dc18f4490c..a0011d4fd9 100644 --- a/R/validate_inputs.R +++ b/R/validate_inputs.R @@ -193,3 +193,45 @@ any_names <- function(x) { } ) } + +#' Validate input +#' +#' @param inputId (`character`) Character of input ID(s) to validate +#' @param condition (`logical(1)`, `function(x)`) Logical value or function returning logical value. +#' Condition should determine expected state, `FALSE` throws. +#' @param message (`character(1)`) Character string of validation message to display +#' @param session Shiny session object +#' +#' @return `TRUE` or `shiny.silent.error` when condition is not met +#' +#' @keywords internal +validate_input <- function(inputId, + condition = function(x) TRUE, + message = "", + session = shiny::getDefaultReactiveDomain()) { + checkmate::assert_character(inputId, min.len = 1) + checkmate::assert( + checkmate::check_flag(condition), + checkmate::check_function(condition, nargs = 1) + ) + checkmate::assert_string(message) + + # Evaluate condition if it's a function + condition_result <- if (is.function(condition)) { + input_value <- session$input[[inputId]] + checkmate::assert_flag(condition(input_value)) + } else { + condition + } + + # Send custom message to JavaScript handler + lapply(inputId, function(id) { + session$sendCustomMessage("validateInput", list( + inputId = session$ns(id), + isValid = condition, + message = message + )) + }) + + validate(need(condition, message)) +} diff --git a/inst/css/validation.css b/inst/css/validation.css index 665fac2d73..9b96617db2 100644 --- a/inst/css/validation.css +++ b/inst/css/validation.css @@ -40,10 +40,10 @@ padding: 0.5em 0 0.5em 0.5em; } -.teal_primary_col > .teal_validated:has(.teal-output-warning), -.teal_primary_col > .teal_validated:has(.shiny-output-error) { +.teal_primary_col>.teal_validated:has(.teal-output-warning), +.teal_primary_col>.teal_validated:has(.shiny-output-error) { width: 100%; background-color: rgba(223, 70, 97, 0.1); border: 1px solid red; padding: 1em; -} +} \ No newline at end of file diff --git a/inst/js/extendShinyJs.js b/inst/js/extendShinyJs.js deleted file mode 100644 index 0c1f9f91f1..0000000000 --- a/inst/js/extendShinyJs.js +++ /dev/null @@ -1,22 +0,0 @@ -// This file contains functions that should be executed at the start of each session, -// not included in the original HTML - -shinyjs.autoFocusModal = function(id) { - document.getElementById('shiny-modal').addEventListener( - 'shown.bs.modal', - () => document.getElementById(id).focus(), - { once: true } - ); -} - -shinyjs.enterToSubmit = function(id, submit_id) { - document.getElementById('shiny-modal').addEventListener( - 'shown.bs.modal', - () => document.getElementById(id).addEventListener('keyup', (e) => { - if (e.key === 'Enter') { - e.preventDefault(); // prevent form submission - document.getElementById(submit_id).click(); - } - }) - ); -} diff --git a/inst/js/input-validator.js b/inst/js/input-validator.js new file mode 100644 index 0000000000..0a9760d278 --- /dev/null +++ b/inst/js/input-validator.js @@ -0,0 +1,29 @@ +$(document).on('shiny:connected', function () { + Shiny.addCustomMessageHandler('validateInput', function (data) { + var inputId = data.inputId; + var isValid = data.isValid; + var message = data.message; + + // Try both CSS selector patterns + var selector1 = '.shiny-input-container#' + inputId; + var selector2 = '.shiny-input-container:has(#' + inputId + ')'; + + var container = $(selector1); + if (container.length === 0) { + container = $(selector2); + } + + if (container.length > 0) { + // Remove existing validation message + container.find('.shiny-output-error').remove(); + + // Add validation message if rule failed + if (!isValid && message && message.trim() !== '') { + var validationSpan = $('').addClass('shiny-output-error').text(message); + container.append(validationSpan); + } + } else { + console.warn('Container not found for input: ' + inputId); + } + }); +}); \ No newline at end of file diff --git a/man/validate_input.Rd b/man/validate_input.Rd new file mode 100644 index 0000000000..6901e274ea --- /dev/null +++ b/man/validate_input.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/validate_inputs.R +\name{validate_input} +\alias{validate_input} +\title{Validate input} +\usage{ +validate_input( + inputId, + condition = function(x) TRUE, + message = "", + session = shiny::getDefaultReactiveDomain() +) +} +\arguments{ +\item{inputId}{(\code{character}) Character of input ID(s) to validate} + +\item{condition}{(\code{logical(1)}, \verb{function(x)}) Logical value or function returning logical value. +Condition should determine expected state, \code{FALSE} throws.} + +\item{message}{(\code{character(1)}) Character string of validation message to display} + +\item{session}{Shiny session object} +} +\value{ +\code{TRUE} or \code{shiny.silent.error} when condition is not met +} +\description{ +Validate input +} +\keyword{internal} From 117309f09bd832c69898f77f262cc8f81052eb67 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Wed, 24 Sep 2025 15:09:53 +0200 Subject: [PATCH 06/33] cleanup badge_dropdown --- R/standard_layout.R | 133 ++---------------------------------- inst/css/badge-dropdown.css | 66 ++++++++++++++++++ inst/js/badge-dropdown.js | 31 +++++++++ 3 files changed, 102 insertions(+), 128 deletions(-) create mode 100644 inst/css/badge-dropdown.css create mode 100644 inst/js/badge-dropdown.js diff --git a/R/standard_layout.R b/R/standard_layout.R index 118ce4e2fe..a9d268bf45 100644 --- a/R/standard_layout.R +++ b/R/standard_layout.R @@ -67,52 +67,10 @@ teal_nav_item <- function(label = NULL, ...) { badge_dropdown <- function(id, label, content) { ns <- shiny::NS(id) htmltools::tagList( - htmltools::tags$style(" - .badge-dropdown-wrapper:has(.shiny-validation-message), - .badge-dropdown-wrapper:has(.shiny-output-error), - .badge-dropdown-wrapper:has(* .shiny-validation-message), - .badge-dropdown-wrapper:has(* .shiny-output-error) { - border-color: red !important; - } - .badge-dropdown { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - border-radius: 0.375rem; - line-height: 1.2; - min-width: auto; - width: 130px; - overflow-x: auto; - white-space: nowrap; - position: relative; - padding-right: .5rem; - } - .badge-dropdown-label { - display: block; - max-width: calc(100% - .5rem); - overflow-x: auto; - white-space: nowrap; - scrollbar-width: none; - -ms-overflow-style: none; - text-align: left; - } - .badge-dropdown-label::-webkit-scrollbar { - display: none; - } - .badge-dropdown-icon { - position: absolute; - top: 50%; - right: 0.5rem; - transform: translateY(-50%); - background: inherit; - z-index: 1; - pointer-events: none; - opacity: 0; - transition: opacity 0.2s ease; - } - .badge-dropdown:hover .badge-dropdown-icon { - opacity: 1; - } - "), + htmltools::singleton(htmltools::tags$head( + htmltools::includeCSS(system.file("css", "badge-dropdown.css", package = "teal")), + htmltools::includeScript(system.file("js", "badge-dropdown.js", package = "teal")) + )), htmltools::tags$div( class = "badge-dropdown-wrapper", htmltools::tags$span( @@ -120,42 +78,7 @@ badge_dropdown <- function(id, label, content) { class = "badge bg-primary rounded-pill badge-dropdown", tags$span(class = "badge-dropdown-label", label), tags$span(class = "badge-dropdown-icon", bsicons::bs_icon("caret-down-fill")), - onclick = sprintf( - " - var container = document.getElementById('%s'); - var summary = document.getElementById('%s'); - - if(container.style.visibility === 'hidden' || container.style.visibility === '') { - container.style.visibility = 'visible'; - container.style.opacity = '1'; - container.style.pointerEvents = 'auto'; - $(container).trigger('shown'); - Shiny.bindAll(container); - - - // Add click outside handler - setTimeout(function() { - function handleClickOutside(event) { - if (!container.contains(event.target) && !summary.contains(event.target)) { - container.style.visibility = 'hidden'; - container.style.opacity = '0'; - container.style.pointerEvents = 'none'; - $(container).trigger('hidden'); - document.removeEventListener('click', handleClickOutside); - } - } - document.addEventListener('click', handleClickOutside); - }, 10); - } else { - container.style.visibility = 'hidden'; - container.style.opacity = '0'; - container.style.pointerEvents = 'none'; - $(container).trigger('hidden'); - } - ", - ns("inputs_container"), - ns("summary_badge") - ) + onclick = sprintf("toggleBadgeDropdown('%s', '%s')", ns("summary_badge"), ns("inputs_container")) ), htmltools::tags$div( content, @@ -165,49 +88,3 @@ badge_dropdown <- function(id, label, content) { ) ) } - -# this one uses bootstrap but doesn't work for some reason -.badge_dropdown <- function(id, label, ...) { - checkmate::assert_list(list(...), c("shiny.tag", "shiny.tag.list", "html", "character")) - ns <- shiny::NS(id) - - btn_id <- ns("dropdown_menu") - - htmltools::tagList( - htmltools::tags$style(" - .badge-dropdown:has(~ div .shiny-validation-message) { - border-color: red !important; - } - .badge-dropdown .btn { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - border-radius: 0.375rem; - line-height: 1.2; - min-width: auto; - } - .badge-dropdown .btn::after { - margin-left: 0.25rem; - vertical-align: 0.1em; - } - "), - tags$div( - class = "dropdown badge-dropdown", - tags$a( - id = btn_id, - class = "btn btn-primary btn-sm dropdown-toggle", - href = "#", - role = "button", - `data-bs-toggle` = "dropdown", - `data-toggle` = "dropdown", - `aria-haspopup` = TRUE, - `aria-expanded` = FALSE, - label - ), - tags$div( - class = "dropdown-menu", - `aria-labelledby` = btn_id, - rlang::list2(...) - ) - ) - ) -} diff --git a/inst/css/badge-dropdown.css b/inst/css/badge-dropdown.css new file mode 100644 index 0000000000..489d9cbed7 --- /dev/null +++ b/inst/css/badge-dropdown.css @@ -0,0 +1,66 @@ +.badge-dropdown-wrapper:has(.shiny-validation-message), +.badge-dropdown-wrapper:has(.shiny-output-error), +.badge-dropdown-wrapper:has(* .shiny-validation-message), +.badge-dropdown-wrapper:has(* .shiny-output-error) { + border-color: red !important; +} + +.badge-dropdown { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + border-radius: 0.375rem; + line-height: 1.2; + min-width: auto; + width: 130px; + overflow-x: auto; + white-space: nowrap; + position: relative; + padding-right: .5rem; +} + +.badge-dropdown-label { + display: block; + max-width: calc(100% - .5rem); + overflow-x: auto; + white-space: nowrap; + scrollbar-width: none; + -ms-overflow-style: none; + text-align: left; +} + +.badge-dropdown-label::-webkit-scrollbar { + display: none; +} + +.badge-dropdown-icon { + position: absolute; + top: 50%; + right: 0.5rem; + transform: translateY(-50%); + background: inherit; + z-index: 1; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s ease; +} + +.badge-dropdown:hover .badge-dropdown-icon { + opacity: 1; +} + +.badge-dropdown:has(~ div .shiny-validation-message) { + border-color: red !important; +} + +.badge-dropdown .btn { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + border-radius: 0.375rem; + line-height: 1.2; + min-width: auto; +} + +.badge-dropdown .btn::after { + margin-left: 0.25rem; + vertical-align: 0.1em; +} \ No newline at end of file diff --git a/inst/js/badge-dropdown.js b/inst/js/badge-dropdown.js new file mode 100644 index 0000000000..d044f7098c --- /dev/null +++ b/inst/js/badge-dropdown.js @@ -0,0 +1,31 @@ +function toggleBadgeDropdown(summaryId, containerId) { + var container = document.getElementById(containerId); + var summary = document.getElementById(summaryId); + + if(container.style.visibility === 'hidden' || container.style.visibility === '') { + container.style.visibility = 'visible'; + container.style.opacity = '1'; + container.style.pointerEvents = 'auto'; + $(container).trigger('shown'); + Shiny.bindAll(container); + + // Add click outside handler + setTimeout(function() { + function handleClickOutside(event) { + if (!container.contains(event.target) && !summary.contains(event.target)) { + container.style.visibility = 'hidden'; + container.style.opacity = '0'; + container.style.pointerEvents = 'none'; + $(container).trigger('hidden'); + document.removeEventListener('click', handleClickOutside); + } + } + document.addEventListener('click', handleClickOutside); + }, 10); + } else { + container.style.visibility = 'hidden'; + container.style.opacity = '0'; + container.style.pointerEvents = 'none'; + $(container).trigger('hidden'); + } +} \ No newline at end of file From 598bb0940038f40bfa895e93f1254d94dbd22a94 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Thu, 25 Sep 2025 10:30:15 +0200 Subject: [PATCH 07/33] fix badge-dropdown to have red border when validation error appears --- inst/css/badge-dropdown.css | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/inst/css/badge-dropdown.css b/inst/css/badge-dropdown.css index 489d9cbed7..24f12a91f6 100644 --- a/inst/css/badge-dropdown.css +++ b/inst/css/badge-dropdown.css @@ -1,10 +1,3 @@ -.badge-dropdown-wrapper:has(.shiny-validation-message), -.badge-dropdown-wrapper:has(.shiny-output-error), -.badge-dropdown-wrapper:has(* .shiny-validation-message), -.badge-dropdown-wrapper:has(* .shiny-output-error) { - border-color: red !important; -} - .badge-dropdown { padding: 0.25rem 0.5rem; font-size: 0.75rem; @@ -18,6 +11,11 @@ padding-right: .5rem; } +.badge-dropdown:has(~ * .shiny-validation-message), +.badge-dropdown:has(~ * .shiny-output-error) { + border: 2px solid red; +} + .badge-dropdown-label { display: block; max-width: calc(100% - .5rem); @@ -48,9 +46,6 @@ opacity: 1; } -.badge-dropdown:has(~ div .shiny-validation-message) { - border-color: red !important; -} .badge-dropdown .btn { padding: 0.25rem 0.5rem; From 8596df61d1e0829c6bdf86527980e2cac2e3c033 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 7 Oct 2025 13:47:52 +0200 Subject: [PATCH 08/33] - multiple variables concatenates values - teal_transform_module to not use badge_dropdown --- R/teal.transform_wrappers.R | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/R/teal.transform_wrappers.R b/R/teal.transform_wrappers.R index 49400977ab..b896998172 100644 --- a/R/teal.transform_wrappers.R +++ b/R/teal.transform_wrappers.R @@ -7,28 +7,46 @@ teal_transform_filter <- function(x, label = "Filter") { label = label, ui <- function(id) { ns <- NS(id) - teal.transform::module_input_ui(ns("transformer"), spec = x) + teal.transform::module_input_ui(ns("transformer"), spec = x, container = div) }, server <- function(id, data) { moduleServer(id, function(input, output, session) { selector <- teal.transform::module_input_srv("transformer", spec = x, data = data) reactive({ req(data(), selector()) - teal.code::eval_code(data(), .make_filter_call(selector())) + # todo: make sure filter call is not executed when setequal(selected, all_possible_choices) + filter_call <- .make_filter_call( + datasets = selector()$datasets$selected, + variables = selector()$variables$selected, + values = selector()$values$selected + ) + teal.code::eval_code(data(), filter_call) }) }) } ) } -.make_filter_call <- function(x) { - checkmate::assert_class(x, "picks") +.make_filter_call <- function(datasets, variables, values) { + checkmate::assert_character(datasets) + checkmate::assert_character(variables) + checkmate::assert_character(values) substitute( dataname <- dplyr::filter(dataname, varname %in% values), list( - dataname = str2lang(x$datasets$selected), - varname = str2lang(x$variables$selected), - values = x$values$selected + dataname = as.name(datasets), + varname = if (length(variables) == 1) { + as.name(variables) + } else { + as.call( + c( + quote(paste), + lapply(variables, as.name), + list(sep = ", ") + ) + ) + }, + values = values ) ) } From 827e43bbd6ceffb19cfe224e36d6ed94f9d7fbe7 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 21 Oct 2025 15:58:36 +0200 Subject: [PATCH 09/33] remove badge_dropdown from teal and make private in teal.transform --- NAMESPACE | 1 - R/standard_layout.R | 36 ---------------------- R/teal.transform_wrappers.R | 4 +-- inst/css/badge-dropdown.css | 61 ------------------------------------- inst/js/badge-dropdown.js | 31 ------------------- man/badge_dropdown.Rd | 21 ------------- 6 files changed, 2 insertions(+), 152 deletions(-) delete mode 100644 inst/css/badge-dropdown.css delete mode 100644 inst/js/badge-dropdown.js delete mode 100644 man/badge_dropdown.Rd diff --git a/NAMESPACE b/NAMESPACE index 24de31628f..d78c12e637 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,7 +21,6 @@ export(add_landing_modal) export(after) export(as.teal_slices) export(as_tdata) -export(badge_dropdown) export(build_app_title) export(disable_report) export(disable_src) diff --git a/R/standard_layout.R b/R/standard_layout.R index a9d268bf45..398b1bcca0 100644 --- a/R/standard_layout.R +++ b/R/standard_layout.R @@ -52,39 +52,3 @@ teal_nav_item <- function(label = NULL, ...) { tagList(list(...)) ) } - -#' Dropdown badge -#' -#' Dropdown button in a form of a badge with `bg-primary` as default style -#' Clicking badge shows a dropdown containing any `HTML` element. Folded dropdown -#' doesn't trigger display output which means that items rendered using `render*` -#' will be recomputed only when dropdown is show. -#' -#' @param id (`character(1)`) shiny module's id -#' @param label (`shiny.tag`) Label displayed on a badge. -#' @param ... (`shiny.tag`) Content of a dropdown. -#' @export -badge_dropdown <- function(id, label, content) { - ns <- shiny::NS(id) - htmltools::tagList( - htmltools::singleton(htmltools::tags$head( - htmltools::includeCSS(system.file("css", "badge-dropdown.css", package = "teal")), - htmltools::includeScript(system.file("js", "badge-dropdown.js", package = "teal")) - )), - htmltools::tags$div( - class = "badge-dropdown-wrapper", - htmltools::tags$span( - id = ns("summary_badge"), - class = "badge bg-primary rounded-pill badge-dropdown", - tags$span(class = "badge-dropdown-label", label), - tags$span(class = "badge-dropdown-icon", bsicons::bs_icon("caret-down-fill")), - onclick = sprintf("toggleBadgeDropdown('%s', '%s')", ns("summary_badge"), ns("inputs_container")) - ), - htmltools::tags$div( - content, - id = ns("inputs_container"), - style = "visibility: hidden; opacity: 0; pointer-events: none; position: absolute; background: white; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 10px; z-index: 1000; min-width: 200px; transition: opacity 0.2s ease;", - ) - ) - ) -} diff --git a/R/teal.transform_wrappers.R b/R/teal.transform_wrappers.R index b896998172..a2eb34ad09 100644 --- a/R/teal.transform_wrappers.R +++ b/R/teal.transform_wrappers.R @@ -7,11 +7,11 @@ teal_transform_filter <- function(x, label = "Filter") { label = label, ui <- function(id) { ns <- NS(id) - teal.transform::module_input_ui(ns("transformer"), spec = x, container = div) + teal.transform::picks_ui(ns("transformer"), spec = x, container = div) }, server <- function(id, data) { moduleServer(id, function(input, output, session) { - selector <- teal.transform::module_input_srv("transformer", spec = x, data = data) + selector <- teal.transform::picks_srv("transformer", spec = x, data = data) reactive({ req(data(), selector()) # todo: make sure filter call is not executed when setequal(selected, all_possible_choices) diff --git a/inst/css/badge-dropdown.css b/inst/css/badge-dropdown.css deleted file mode 100644 index 24f12a91f6..0000000000 --- a/inst/css/badge-dropdown.css +++ /dev/null @@ -1,61 +0,0 @@ -.badge-dropdown { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - border-radius: 0.375rem; - line-height: 1.2; - min-width: auto; - width: 130px; - overflow-x: auto; - white-space: nowrap; - position: relative; - padding-right: .5rem; -} - -.badge-dropdown:has(~ * .shiny-validation-message), -.badge-dropdown:has(~ * .shiny-output-error) { - border: 2px solid red; -} - -.badge-dropdown-label { - display: block; - max-width: calc(100% - .5rem); - overflow-x: auto; - white-space: nowrap; - scrollbar-width: none; - -ms-overflow-style: none; - text-align: left; -} - -.badge-dropdown-label::-webkit-scrollbar { - display: none; -} - -.badge-dropdown-icon { - position: absolute; - top: 50%; - right: 0.5rem; - transform: translateY(-50%); - background: inherit; - z-index: 1; - pointer-events: none; - opacity: 0; - transition: opacity 0.2s ease; -} - -.badge-dropdown:hover .badge-dropdown-icon { - opacity: 1; -} - - -.badge-dropdown .btn { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - border-radius: 0.375rem; - line-height: 1.2; - min-width: auto; -} - -.badge-dropdown .btn::after { - margin-left: 0.25rem; - vertical-align: 0.1em; -} \ No newline at end of file diff --git a/inst/js/badge-dropdown.js b/inst/js/badge-dropdown.js deleted file mode 100644 index d044f7098c..0000000000 --- a/inst/js/badge-dropdown.js +++ /dev/null @@ -1,31 +0,0 @@ -function toggleBadgeDropdown(summaryId, containerId) { - var container = document.getElementById(containerId); - var summary = document.getElementById(summaryId); - - if(container.style.visibility === 'hidden' || container.style.visibility === '') { - container.style.visibility = 'visible'; - container.style.opacity = '1'; - container.style.pointerEvents = 'auto'; - $(container).trigger('shown'); - Shiny.bindAll(container); - - // Add click outside handler - setTimeout(function() { - function handleClickOutside(event) { - if (!container.contains(event.target) && !summary.contains(event.target)) { - container.style.visibility = 'hidden'; - container.style.opacity = '0'; - container.style.pointerEvents = 'none'; - $(container).trigger('hidden'); - document.removeEventListener('click', handleClickOutside); - } - } - document.addEventListener('click', handleClickOutside); - }, 10); - } else { - container.style.visibility = 'hidden'; - container.style.opacity = '0'; - container.style.pointerEvents = 'none'; - $(container).trigger('hidden'); - } -} \ No newline at end of file diff --git a/man/badge_dropdown.Rd b/man/badge_dropdown.Rd deleted file mode 100644 index 4214eedd90..0000000000 --- a/man/badge_dropdown.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/standard_layout.R -\name{badge_dropdown} -\alias{badge_dropdown} -\title{Dropdown badge} -\usage{ -badge_dropdown(id, label, content) -} -\arguments{ -\item{id}{(\code{character(1)}) shiny module's id} - -\item{label}{(\code{shiny.tag}) Label displayed on a badge.} - -\item{...}{(\code{shiny.tag}) Content of a dropdown.} -} -\description{ -Dropdown button in a form of a badge with \code{bg-primary} as default style -Clicking badge shows a dropdown containing any \code{HTML} element. Folded dropdown -doesn't trigger display output which means that items rendered using \verb{render*} -will be recomputed only when dropdown is show. -} From f5348a0571e536f43f6052598238104266604181 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Fri, 24 Oct 2025 12:40:19 +0200 Subject: [PATCH 10/33] spec -> picks --- R/teal.transform_wrappers.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/teal.transform_wrappers.R b/R/teal.transform_wrappers.R index a2eb34ad09..cb7db98991 100644 --- a/R/teal.transform_wrappers.R +++ b/R/teal.transform_wrappers.R @@ -7,11 +7,11 @@ teal_transform_filter <- function(x, label = "Filter") { label = label, ui <- function(id) { ns <- NS(id) - teal.transform::picks_ui(ns("transformer"), spec = x, container = div) + teal.transform::picks_ui(ns("transformer"), picks = x, container = div) }, server <- function(id, data) { moduleServer(id, function(input, output, session) { - selector <- teal.transform::picks_srv("transformer", spec = x, data = data) + selector <- teal.transform::picks_srv("transformer", picks = x, data = data) reactive({ req(data(), selector()) # todo: make sure filter call is not executed when setequal(selected, all_possible_choices) From c162d7b7f9512d4b8517ab02ac3c0409bc9b258f Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Wed, 29 Oct 2025 09:15:56 +0100 Subject: [PATCH 11/33] revert unnecessary --- DESCRIPTION | 2 - NAMESPACE | 3 -- R/after.R | 38 ------------- R/module_nested_tabs.R | 101 +++++++++++++++-------------------- R/module_transform_data.R | 42 +++++++-------- R/standard_layout.R | 54 ------------------- R/teal.transform_wrappers.R | 52 ------------------ R/zzz.R | 1 - man/module_transform_data.Rd | 6 +-- 9 files changed, 64 insertions(+), 235 deletions(-) delete mode 100644 R/standard_layout.R delete mode 100644 R/teal.transform_wrappers.R diff --git a/DESCRIPTION b/DESCRIPTION index 779382dd82..9d86354263 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -124,10 +124,8 @@ Collate: 'module_transform_data.R' 'reporter_previewer_module.R' 'show_rcode_modal.R' - 'standard_layout.R' 'tdata.R' 'teal.R' - 'teal.transform_wrappers.R' 'teal_data_module.R' 'teal_data_module-eval_code.R' 'teal_data_module-within.R' diff --git a/NAMESPACE b/NAMESPACE index d78c12e637..e55639efc8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -43,12 +43,9 @@ export(srv_session_info) export(srv_teal) export(srv_teal_with_splash) export(srv_transform_teal_data) -export(standard_layout2) export(tdata2env) export(teal_data_module) -export(teal_nav_item) export(teal_slices) -export(teal_transform_filter) export(teal_transform_module) export(ui_session_info) export(ui_teal) diff --git a/R/after.R b/R/after.R index ad9f25a45d..1c0b4abdc2 100644 --- a/R/after.R +++ b/R/after.R @@ -121,41 +121,3 @@ after_srv <- function(old, new, additional_args) { formals(new_srv) <- formals(old) new_srv } - -.before_server <- function(x, y, additional_args) { - # add `_`-prefix to make sure objects are not masked in the wrapper functions - `_x` <- x # nolint: object_name. - `_y` <- y # nolint: object_name. - new_x <- function(id, ...) { - original_args <- as.list(environment()) - original_args$id <- "wrapped" - if ("..." %in% names(formals(`_x`))) { - original_args <- c(original_args, list(...)) - } - moduleServer(id, function(input, output, session) { - original_out <- if (all(c("input", "output", "session") %in% names(formals(`_x`)))) { - original_args$module <- `_x` - do.call(shiny::callModule, args = original_args) - } else { - do.call(`_x`, original_args) - } - original_out_r <- reactive( - if (is.reactive(original_out)) { - original_out() - } else { - original_out - } - ) - wrapper_args <- utils::modifyList( - additional_args, - list(id = "wrapper", input = input, output = output, session = session) - ) - reactive({ - wrapper_args$data <- req(original_out_r()) - do.call(`_y`, wrapper_args[names(formals(`_y`))], quote = TRUE) - }) - }) - } - formals(new_x) <- formals(x) - new_x -} diff --git a/R/module_nested_tabs.R b/R/module_nested_tabs.R index 8b210f6831..e4387d2027 100644 --- a/R/module_nested_tabs.R +++ b/R/module_nested_tabs.R @@ -188,58 +188,7 @@ srv_teal_module <- function(id, .ui_teal_module.teal_module <- function(id, modules, active_module_id) { ns <- NS(id) args <- c(list(id = ns("module")), modules$ui_args) - - module_ui <- do.call(what = modules$ui, args = args, quote = TRUE) - is_transform_relocated <- !identical(getOption("teal.transform.location"), ".teal-sidebar") && - length(htmltools::tagQuery(module_ui)$find(getOption("teal.transform.location"))$selectedTags()) - - container_id <- ns("wrapper") - module_id <- modules$path - - data_summary_ui <- tags$div( - class = "teal-active-data-summary-panel", - bslib::accordion( - id = ns("data_summary_accordion"), - bslib::accordion_panel( - "Active Data Summary", - tags$div( - class = "teal-active-data-summary", - ui_data_summary(ns("data_summary")) - ) - ) - ) - ) - - transformators_ui <- if (length(modules$transformators) > 0 && !isTRUE(attr(modules$transformators, "custom_ui"))) { - ui_transform_teal_data( - ns("data_transform"), - transformators = modules$transformators, - container = if (is_transform_relocated) "div" else "accordion" - ) - } - - if (is_transform_relocated) { - module_ui <- htmltools::tagInsertChildren( - tag = module_ui, - transformators_ui, - .cssSelector = getOption("teal.transform.location"), - after = 0 - ) - transformators_wrapper_ui <- NULL - } else { - transformators_wrapper_ui <- tags$div( - tags$br(), - tags$div( - class = "teal-transform-panel", - bslib::accordion( - id = ns("data_transform_accordion"), - bslib::accordion_panel("Transform Data", transformators_ui) - ) - ) - ) - } - - module_wrapper_ui <- tags$div( + ui_teal <- tags$div( shinyjs::hidden( tags$div( id = ns("transform_failure_info"), @@ -256,9 +205,11 @@ srv_teal_module <- function(id, class = "teal_validated", ui_check_module_datanames(ns("validate_datanames")) ), - module_ui + do.call(what = modules$ui, args = args, quote = TRUE) ) ) + container_id <- ns("wrapper") + module_id <- modules$path link <- tags$li( tags$a( @@ -294,13 +245,45 @@ srv_teal_module <- function(id, position = getOption("teal.sidebar.position", "left"), width = getOption("teal.sidebar.width", 250), tags$div( - data_summary_ui, + tags$div( + class = "teal-active-data-summary-panel", + bslib::accordion( + id = ns("data_summary_accordion"), + bslib::accordion_panel( + "Active Data Summary", + tags$div( + class = "teal-active-data-summary", + ui_data_summary(ns("data_summary")) + ) + ) + ) + ), tags$br(), - tags$div(class = "teal-filter-panel", ui_filter_data(ns("filter_panel"))), - transformators_wrapper_ui + tags$div( + class = "teal-filter-panel", + ui_filter_data(ns("filter_panel")) + ), + if (length(modules$transformators) > 0 && !isTRUE(attr(modules$transformators, "custom_ui"))) { + tags$div( + tags$br(), + tags$div( + class = "teal-transform-panel", + bslib::accordion( + id = ns("data_transform_accordion"), + bslib::accordion_panel( + "Transform Data", + ui_transform_teal_data( + ns("data_transform"), + transformators = modules$transformators + ) + ) + ) + ) + ) + } ) ), - module_wrapper_ui + ui_teal ), div( id = ns("sidebar_toggle_buttons"), @@ -315,7 +298,7 @@ srv_teal_module <- function(id, ns("data_filters_toggle"), icon("fas fa-filter") ), - if (!is_transform_relocated && length(modules$transformators) > 0) { + if (length(modules$transformators) > 0) { actionButton( class = "data-transforms-toggle btn-outline-primary", ns("data_transforms_toggle"), @@ -338,7 +321,7 @@ srv_teal_module <- function(id, ) ) } else { - module_ui + ui_teal } ) ) diff --git a/R/module_transform_data.R b/R/module_transform_data.R index 2e4c604fe6..26b49f0b47 100644 --- a/R/module_transform_data.R +++ b/R/module_transform_data.R @@ -14,9 +14,8 @@ NULL #' @export #' @rdname module_transform_data -ui_transform_teal_data <- function(id, transformators, container = c("accordion", "div")) { +ui_transform_teal_data <- function(id, transformators, class = "well") { checkmate::assert_string(id) - container <- match.arg(container) if (length(transformators) == 0L) { return(NULL) } @@ -34,32 +33,29 @@ ui_transform_teal_data <- function(id, transformators, container = c("accordion" data_mod <- transformators[[name]] transform_wrapper_id <- ns(sprintf("wrapper_%s", name)) - transformator_ui <- tags$div( - id = transform_wrapper_id, - if (is.null(data_mod$ui)) { - return(NULL) - } else { - data_mod$ui(id = ns("transform")) - }, - div( - id = ns("validate_messages"), - class = "teal_validated", - uiOutput(ns("error_wrapper")) - ) - ) + display_fun <- if (is.null(data_mod$ui)) shinyjs::hidden else function(x) x - transformator_wrapper <- if (container == "accordion") { + display_fun( bslib::accordion( bslib::accordion_panel( - title = attr(data_mod, "label"), + attr(data_mod, "label"), icon = bsicons::bs_icon("palette-fill"), - transformator_ui + tags$div( + id = transform_wrapper_id, + if (is.null(data_mod$ui)) { + return(NULL) + } else { + data_mod$ui(id = ns("transform")) + }, + div( + id = ns("validate_messages"), + class = "teal_validated", + uiOutput(ns("error_wrapper")) + ) + ) ) ) - } else { - teal::teal_nav_item(transformator_ui, label = tags$strong(attr(data_mod, "label"))) - } - if (is.null(data_mod$ui)) shinyjs::hidden(transformator_wrapper) else transformator_wrapper + ) } ) } @@ -87,7 +83,7 @@ srv_transform_teal_data <- function(id, data, transformators, modules = NULL, is data_out <- reactiveVal() .call_once_when(inherits(data_previous(), "teal_data"), { - logger::log_debug("srv_transform_teal_data@2 triggering a transform module call for { name }.") + logger::log_debug("srv_teal_transform_teal_data@2 triggering a transform module call for { name }.") data_unhandled <- transformators[[name]]$server("transform", data = data_previous) data_handled <- reactive(tryCatch(data_unhandled(), error = function(e) e)) diff --git a/R/standard_layout.R b/R/standard_layout.R deleted file mode 100644 index 398b1bcca0..0000000000 --- a/R/standard_layout.R +++ /dev/null @@ -1,54 +0,0 @@ -#' @rdname standard_layout -#' @export -standard_layout2 <- function(output, - encoding = NULL, - forms = NULL, - pre_output = NULL, - post_output = NULL) { - checkmate::assert_multi_class(output, c("shiny.tag", "shiny.tag.list", "html")) - checkmate::assert_multi_class(encoding, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) - checkmate::assert_multi_class(pre_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) - checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE) - - # if encoding=NULL then forms is placed below output - - tag_output <- tags$div( - class = "teal standard-layout", - tags$div(class = "standard-layout-pre-output", pre_output), - tags$div(class = "standard-layout-output", output), - tags$div(class = "standard-layout-post-output", post_output) - ) - - - output_panel <- tags$div(output, class = "teal standard-layout output-panel") - out <- if (!is.null(encoding)) { - tags$div( - tags$nav( - class = "navbar navbar-expand-lg navbar-light bg-light", - tags$div( - class = "navbar-nav d-flex flex-row flex-nowrap teal standard-layout encoding-panel", - style = "width: 100%;", - encoding - ) - ), - output_panel - ) - } else { - output_panel - } - - - bslib::page_fluid(out, class = "teal standard-layout-wrapper") -} - -#' @export -teal_nav_item <- function(label = NULL, ...) { - checkmate::assert_list(list(...), c("shiny.tag", "shiny.tag.list", "html", "character")) - checkmate::assert_multi_class(label, c("shiny.tag", "shiny.tag.list", "html", "character"), null.ok = TRUE) - tags$div( - class = "nav-item", - style = "min-width: 150px;", - label, - tagList(list(...)) - ) -} diff --git a/R/teal.transform_wrappers.R b/R/teal.transform_wrappers.R deleted file mode 100644 index cb7db98991..0000000000 --- a/R/teal.transform_wrappers.R +++ /dev/null @@ -1,52 +0,0 @@ -#' @export -teal_transform_filter <- function(x, label = "Filter") { - checkmate::assert_class(x, "picks") - checkmate::assert_true("values" %in% names(x)) - - teal_transform_module( - label = label, - ui <- function(id) { - ns <- NS(id) - teal.transform::picks_ui(ns("transformer"), picks = x, container = div) - }, - server <- function(id, data) { - moduleServer(id, function(input, output, session) { - selector <- teal.transform::picks_srv("transformer", picks = x, data = data) - reactive({ - req(data(), selector()) - # todo: make sure filter call is not executed when setequal(selected, all_possible_choices) - filter_call <- .make_filter_call( - datasets = selector()$datasets$selected, - variables = selector()$variables$selected, - values = selector()$values$selected - ) - teal.code::eval_code(data(), filter_call) - }) - }) - } - ) -} - -.make_filter_call <- function(datasets, variables, values) { - checkmate::assert_character(datasets) - checkmate::assert_character(variables) - checkmate::assert_character(values) - substitute( - dataname <- dplyr::filter(dataname, varname %in% values), - list( - dataname = as.name(datasets), - varname = if (length(variables) == 1) { - as.name(variables) - } else { - as.call( - c( - quote(paste), - lapply(variables, as.name), - list(sep = ", ") - ) - ) - }, - values = values - ) - ) -} diff --git a/R/zzz.R b/R/zzz.R index 574ebc9358..25ac700dfb 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -6,7 +6,6 @@ teal.lockfile.mode = "auto", shiny.sanitize.errors = FALSE, teal.sidebar.position = "left", - teal.transform.location = ".teal-sidebar", # alternatively .standard-layout.encoding-panel teal.sidebar.width = 250, teal.reporter.nav_buttons = c("preview", "download", "load", "reset"), teal.show_src = TRUE diff --git a/man/module_transform_data.Rd b/man/module_transform_data.Rd index 23f3f84c80..a0760e10d3 100644 --- a/man/module_transform_data.Rd +++ b/man/module_transform_data.Rd @@ -6,7 +6,7 @@ \alias{srv_transform_teal_data} \title{Module to transform \code{reactive} \code{teal_data}} \usage{ -ui_transform_teal_data(id, transformators, container = c("accordion", "div")) +ui_transform_teal_data(id, transformators, class = "well") srv_transform_teal_data( id, @@ -22,6 +22,8 @@ srv_transform_teal_data( \item{transformators}{(\code{list} of \code{teal_transform_module}) that will be applied to transform module's data input. To learn more check \code{vignette("transform-input-data", package = "teal")}.} +\item{class}{(character(1)) CSS class to be added in the \code{div} wrapper tag.} + \item{data}{(\code{teal_data}, \code{teal_data_module}, or \code{reactive} returning \code{teal_data}) The data which application will depend on.} @@ -30,8 +32,6 @@ The data which application will depend on.} \item{is_transform_failed}{(\code{reactiveValues}) contains \code{logical} flags named after each transformator. Help to determine if any previous transformator failed, so that following transformators can be disabled and display a generic failure message.} - -\item{class}{(character(1)) CSS class to be added in the \code{div} wrapper tag.} } \value{ \code{reactive} \code{teal_data} From 1bed0b1ac43a6d7764f34bf743f3c766c178d937 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Thu, 30 Oct 2025 15:57:27 +0100 Subject: [PATCH 12/33] fix lintr --- R/validate_inputs.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/validate_inputs.R b/R/validate_inputs.R index a0011d4fd9..032a472f70 100644 --- a/R/validate_inputs.R +++ b/R/validate_inputs.R @@ -205,7 +205,7 @@ any_names <- function(x) { #' @return `TRUE` or `shiny.silent.error` when condition is not met #' #' @keywords internal -validate_input <- function(inputId, +validate_input <- function(inputId, # nolint condition = function(x) TRUE, message = "", session = shiny::getDefaultReactiveDomain()) { From 123e4578f5585057984a7f0758287a0c059bad37 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Mon, 3 Nov 2025 11:21:52 +0100 Subject: [PATCH 13/33] placeholders for tests --- R/validate_inputs.R | 12 +- man/validate_input.Rd | 2 +- .../testthat/test-shinytest2-validate_input.R | 124 ++++++++++++ tests/testthat/test-validate_input.R | 188 ++++++++++++++++++ 4 files changed, 319 insertions(+), 7 deletions(-) create mode 100644 tests/testthat/test-shinytest2-validate_input.R create mode 100644 tests/testthat/test-validate_input.R diff --git a/R/validate_inputs.R b/R/validate_inputs.R index 032a472f70..ec2d2e7e8d 100644 --- a/R/validate_inputs.R +++ b/R/validate_inputs.R @@ -202,7 +202,7 @@ any_names <- function(x) { #' @param message (`character(1)`) Character string of validation message to display #' @param session Shiny session object #' -#' @return `TRUE` or `shiny.silent.error` when condition is not met +#' @return `NULL` or `shiny.silent.error` when condition is not met #' #' @keywords internal validate_input <- function(inputId, # nolint @@ -212,14 +212,14 @@ validate_input <- function(inputId, # nolint checkmate::assert_character(inputId, min.len = 1) checkmate::assert( checkmate::check_flag(condition), - checkmate::check_function(condition, nargs = 1) + checkmate::check_function(condition, nargs = length(inputId)) ) checkmate::assert_string(message) # Evaluate condition if it's a function condition_result <- if (is.function(condition)) { - input_value <- session$input[[inputId]] - checkmate::assert_flag(condition(input_value)) + input_value <- lapply(inputId, function(id) session$input[[id]]) + checkmate::assert_flag(do.call(condition, input_value)) } else { condition } @@ -228,10 +228,10 @@ validate_input <- function(inputId, # nolint lapply(inputId, function(id) { session$sendCustomMessage("validateInput", list( inputId = session$ns(id), - isValid = condition, + isValid = condition_result, message = message )) }) - validate(need(condition, message)) + validate(need(condition_result, message)) } diff --git a/man/validate_input.Rd b/man/validate_input.Rd index 6901e274ea..bb7b5e9dde 100644 --- a/man/validate_input.Rd +++ b/man/validate_input.Rd @@ -22,7 +22,7 @@ Condition should determine expected state, \code{FALSE} throws.} \item{session}{Shiny session object} } \value{ -\code{TRUE} or \code{shiny.silent.error} when condition is not met +\code{NULL} or \code{shiny.silent.error} when condition is not met } \description{ Validate input diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R new file mode 100644 index 0000000000..6bba8d18c9 --- /dev/null +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -0,0 +1,124 @@ +testthat::skip_if_not_installed("shinytest2") +testthat::skip_if_not_installed("rvest") + +testthat::test_that("e2e: validate_input displays validation message when input is invalid, no message when valid", { + skip_if_too_deep(5) + + validation_module <- function(label = "validation test") { + module( + label = label, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + output$plot <- renderPlot({ + teal:::validate_input( + inputId = "number", + condition = function(x) !is.null(x) && as.numeric(x) > 5, + message = "Please select a number greater than 5", + session = session + ) + plot(1:10) + }) + }) + }, + ui = function(id) { + ns <- NS(id) + tagList( + numericInput(ns("number"), "Enter a number:", value = 3, min = 1, max = 10), + plotOutput(ns("plot")) + ) + } + ) + } + + app <- TealAppDriver$new( + init( + data = simple_teal_data(), + modules = validation_module(label = "Validation Test") + ) + ) + + # Check that validation error is displayed in the output + error_text <- app$get_text(".shiny-output-error-validation") + testthat::expect_match(error_text, "Please select a number greater than 5") + + + # Update input to valid value + app$set_input(app$namespaces()$module("number"), 7) + app$wait_for_idle() + + # Check that validation error is gone + testthat::expect_null(app$get_text(".shiny-output-error-validation")) + + app$stop() +}) + +testthat::test_that("e2e: validate_input validates many inputs (and linked output) when they return invalid value", { + skip_if_too_deep(5) + + multi_validation_module <- function(label = "multi validation") { + module( + label = label, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + output$result <- renderText({ + teal:::validate_input( + inputId = c("input1", "input2"), + condition = function(x, y) identical(x, y), + message = "x and y must be identical", + session = session + ) + paste("Valid inputs:", input$input1, input$input2) + }) + }) + }, + ui = function(id) { + ns <- NS(id) + tagList( + numericInput(ns("input1"), "Enter y:", value = 4, min = 0, max = 100), + numericInput(ns("input2"), "Enter y:", value = 5, min = 0, max = 100), + textOutput(ns("result")) + ) + } + ) + } + + app <- TealAppDriver$new( + init( + data = simple_teal_data(), + modules = multi_validation_module(label = "Multi Validation") + ) + ) + + # Initially input2 is invalid (value 5 < 10) + error_text <- app$get_text(".shiny-output-error-validation") + testthat::expect_match(error_text, "x and y must be identical") + testthat::expect_match( + app$get_text(app$namespaces(TRUE)$module("result")), + "x and y must be identical" + ) + + # Update input2 to valid value + app$set_input(app$namespaces()$module("input1"), 5) + app$set_input(app$namespaces()$module("input2"), 5) + app$wait_for_idle() + + # Check that validation passes and result is shown + error_text <- app$get_text(".shiny-output-error-validation") + testthat::expect_null(error_text) + + testthat::expect_match( + app$get_text(app$namespaces(TRUE)$module("result")), + "Valid inputs: 5 5" + ) + + app$stop() +}) + +testthat::test_that("e2e: validate_input validates sliderInput") +testthat::test_that("e2e: validate_input validates selectInput") +testthat::test_that("e2e: validate_input validates selectizeInput") +testthat::test_that("e2e: validate_input validates radioButtons") +testthat::test_that("e2e: validate_input validates checkboxGroupInput") +testthat::test_that("e2e: validate_input validates checkboxInput") +testthat::test_that("e2e: validate_input validates dateInput") +testthat::test_that("e2e: validate_input validates dateRangeInput") diff --git a/tests/testthat/test-validate_input.R b/tests/testthat/test-validate_input.R new file mode 100644 index 0000000000..eecd4b9b6e --- /dev/null +++ b/tests/testthat/test-validate_input.R @@ -0,0 +1,188 @@ +testthat::test_that("validate_input(inputId) has to be a character(1)", { + testthat::expect_error( + validate_input(inputId = character(0)), + "Assertion on 'inputId' failed" + ) + testthat::expect_error( + validate_input(inputId = 123), + "Assertion on 'inputId' failed" + ) + testthat::expect_error( + validate_input(inputId = NULL), + "Assertion on 'inputId' failed" + ) +}) + +testthat::test_that("validate_input(condition) has to be a logical(1) or function with nargs = length(inputId)", { + testthat::expect_error( + validate_input(inputId = "test", condition = "not_logical"), + "Assertion failed" + ) + testthat::expect_error( + validate_input(inputId = "test", condition = c(TRUE, FALSE)), + "Assertion failed" + ) + + testthat::expect_error( + validate_input(inputId = "test", condition = function(x, y) TRUE), + "Assertion failed" + ) + + testthat::expect_error( + validate_input(inputId = c("test", "test2"), condition = function(x) TRUE), + "Assertion failed" + ) +}) + +testthat::test_that("validate_input(message) has to be a character(1)", { + testthat::expect_error( + validate_input(inputId = "test", message = 123), + "Assertion on 'message' failed" + ) + testthat::expect_error( + validate_input(inputId = "test", message = c("a", "b")), + "Assertion on 'message' failed" + ) +}) + +testthat::test_that("validate_input returns NULL when condition is TRUE", { + test_module <- function(id) { + moduleServer(id, function(input, output, session) { + result <- reactive({ + validate_input( + inputId = "test_input", + condition = TRUE, + message = "Test message", + session = session + ) + }) + }) + } + + shiny::testServer( + app = test_module, + args = list(id = "test"), + expr = testthat::expect_null(result()) + ) +}) + +testthat::test_that("validate_input throws `message` as shiny.silent.error when `condition = FALSE`", { + test_module <- function(id) { + moduleServer(id, function(input, output, session) { + result <- reactive({ + validate_input( + inputId = "test_input", + condition = FALSE, + message = "Test message", + session = session + ) + }) + }) + } + + shiny::testServer( + app = test_module, + args = list(id = "test"), + expr = testthat::expect_error(result(), "Test message") + ) +}) + +testthat::test_that("validate_input works with function condition that returns `TRUE` for input with `inputId`", { + test_module <- function(id) { + moduleServer(id, function(input, output, session) { + result <- reactive({ + validate_input( + inputId = "test_input", + condition = function(x) identical(x, "valid_value"), + message = "Input is invalid", + session = session + ) + }) + }) + } + + shiny::testServer( + app = test_module, + expr = { + # Set up input value + session$setInputs(test_input = "valid_value") + testthat::expect_null(result()) + }, + args = list(id = "test") + ) +}) + +testthat::test_that("validate_input works with function condition that returns `FALSE` for input with `inputId`", { + test_module <- function(id) { + moduleServer(id, function(input, output, session) { + result <- reactive({ + validate_input( + inputId = "test_input", + condition = function(x) identical(x, "valid_value"), + message = "Input is invalid", + session = session + ) + }) + }) + } + + shiny::testServer( + app = test_module, + expr = { + # Set up input value + session$setInputs(test_input = "invalid_value") + testthat::expect_error(result(), "Input is invalid") + }, + args = list(id = "test") + ) +}) + +testthat::test_that("validate_input with multiple inputIds throws shiny.silent.error when any input is invalid", { + test_module <- function(id) { + moduleServer(id, function(input, output, session) { + result <- reactive({ + validate_input( + inputId = c("test_input1", "test_input2"), + condition = function(x, y) identical(x, y), + message = "Are not identical", + session = session + ) + }) + }) + } + + shiny::testServer( + app = test_module, + expr = { + # Set up input value + session$setInputs(test_input1 = 99, test_input2 = 98) + testthat::expect_error(result(), "Are not identical") + }, + args = list(id = "test") + ) +}) + +testthat::test_that("validate_input with multiple inputIds doesn't throw shiny.silent.error if all inputs are valid", { + test_module <- function(id) { + moduleServer(id, function(input, output, session) { + result <- reactive({ + validate_input( + inputId = c("test_input1", "test_input2"), + condition = function(x, y) identical(x, y), + message = "Input is invalid", + session = session + ) + }) + }) + } + + shiny::testServer( + app = test_module, + expr = { + # Set up input value + session$setInputs(test_input1 = 99, test_input2 = 99) + testthat::expect_no_error(result()) + }, + args = list(id = "test") + ) +}) From 4d868cf136d03425aa4ba248e371065fb526c1a5 Mon Sep 17 00:00:00 2001 From: m7pr Date: Thu, 2 Apr 2026 12:01:19 +0200 Subject: [PATCH 14/33] revert changes after merge --- inst/js/extendShinyJs.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/inst/js/extendShinyJs.js b/inst/js/extendShinyJs.js index f92816b62b..005a318a28 100644 --- a/inst/js/extendShinyJs.js +++ b/inst/js/extendShinyJs.js @@ -1,26 +1,3 @@ -// This file contains functions that should be executed at the start of each session, -// not included in the original HTML - -shinyjs.autoFocusModal = function(id) { - document.getElementById('shiny-modal').addEventListener( - 'shown.bs.modal', - () => document.getElementById(id).focus(), - { once: true } - ); -} - -shinyjs.enterToSubmit = function(id, submit_id) { - document.getElementById('shiny-modal').addEventListener( - 'shown.bs.modal', - () => document.getElementById(id).addEventListener('keyup', (e) => { - if (e.key === 'Enter') { - e.preventDefault(); // prevent form submission - document.getElementById(submit_id).click(); - } - }) - ); -} - // Custom message handler to get document title and send it to the server Shiny.addCustomMessageHandler('teal-get-document-title', function(message) { const title = document.title; From cefc62ce062575d8200163d49aa9d191e89c4359 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 14 Apr 2026 10:25:54 +0200 Subject: [PATCH 15/33] bring back removed js-code --- inst/js/extendShinyJs.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/inst/js/extendShinyJs.js b/inst/js/extendShinyJs.js index 005a318a28..b28a2898e1 100644 --- a/inst/js/extendShinyJs.js +++ b/inst/js/extendShinyJs.js @@ -1,8 +1,30 @@ +// This file contains functions that should be executed at the start of each session, +// not included in the original HTML + +shinyjs.autoFocusModal = function (id) { + document.getElementById('shiny-modal').addEventListener( + 'shown.bs.modal', + () => document.getElementById(id).focus(), + { once: true } + ); +} + +shinyjs.enterToSubmit = function (id, submit_id) { + document.getElementById('shiny-modal').addEventListener( + 'shown.bs.modal', + () => document.getElementById(id).addEventListener('keyup', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); // prevent form submission + document.getElementById(submit_id).click(); + } + }) + ); +} + // Custom message handler to get document title and send it to the server -Shiny.addCustomMessageHandler('teal-get-document-title', function(message) { +Shiny.addCustomMessageHandler('teal-get-document-title', function (message) { const title = document.title; const inputId = message.inputId; console.log('Teal: Sending document title:', title, 'to input:', inputId); - Shiny.setInputValue(inputId, title, {priority: 'event'}); + Shiny.setInputValue(inputId, title, { priority: 'event' }); }); - From 15101b57e8091432e686cb41ab13c296f15a10b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 4 May 2026 13:43:29 +0100 Subject: [PATCH 16/33] feat: adds missing shinytest2 tests --- .../testthat/test-shinytest2-validate_input.R | 136 ++++++++++++++++-- 1 file changed, 128 insertions(+), 8 deletions(-) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index 6bba8d18c9..e796e7463f 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -114,11 +114,131 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu app$stop() }) -testthat::test_that("e2e: validate_input validates sliderInput") -testthat::test_that("e2e: validate_input validates selectInput") -testthat::test_that("e2e: validate_input validates selectizeInput") -testthat::test_that("e2e: validate_input validates radioButtons") -testthat::test_that("e2e: validate_input validates checkboxGroupInput") -testthat::test_that("e2e: validate_input validates checkboxInput") -testthat::test_that("e2e: validate_input validates dateInput") -testthat::test_that("e2e: validate_input validates dateRangeInput") +testthat::describe("e2e: validate_input validates", { + all_inputs_mod <- module( + label = "all inputs", + ui = function(id) { + ns <- NS(id) + tagList( + selectInput(ns("select"), "Select Input:", choices = c("A", "B", "C")), + selectizeInput(ns("selectize"), "Selectize Input:", choices = c("X", "Y", "Z")), + radioButtons(ns("radio"), "Radio Buttons:", choices = c("Option 1", "Option 2")), + checkboxGroupInput(ns("checkbox_group"), "Checkbox Group:", choices = c("Check 1", "Check 2")), + checkboxInput(ns("checkbox"), "Checkbox Input"), + dateInput(ns("date"), "Date Input:"), + dateRangeInput(ns("date_range"), "Date Range Input:") + ) + }, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + reactive({ + validation <- list( + try( + teal:::validate_input("select", function(x) identical(x, "B"), "select must be B"), + silent = TRUE + ), + try( + teal:::validate_input("selectize", function(x) identical(x, "Y"), "selectize must be Y",), + silent = TRUE + ), + try( + teal:::validate_input("radio", function(x) identical(x, "Option 2"), "radio must be Option 2"), + silent = TRUE + ), + try( + teal:::validate_input("checkbox_group", function(x) identical(x, c("Check 2")), "checkbox_group must be Check 2"), + silent = TRUE + ), + try( + teal:::validate_input("checkbox", function(x) identical(x, TRUE), "checkbox must be TRUE"), + silent = TRUE + ), + try( + teal:::validate_input("date", function(x) identical(x, as.Date("2024-01-01")), "date must be 2024-01-01"), + silent = TRUE + ), + try( + teal:::validate_input( + "date_range", + function(x) identical(x, c(as.Date("2024-01-01"), as.Date("2024-01-31"))), + "date_range must be 2024-01-01 to 2024-01-31" + ), + silent = TRUE + ) + ) + data() + }) + }) + } + ) + + app_driver <- TealAppDriver$new(init(data = simple_teal_data(), modules = all_inputs_mod)) + withr::defer(app_driver$stop()) + + it("selectInput", { + message <- "select must be B" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("select"), "B") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("selectizeInput", { + message <- "selectize must be Y" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("selectize"), "Y") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("radioButtons", { + message <- "radio must be Option 2" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("radio"), "Option 2") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("checkboxGroupInput", { + message <- "checkbox_group must be Check 2" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("checkbox_group"), "Check 2") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("checkboxInput", { + message <- "checkbox must be TRUE" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("checkbox"), TRUE) + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("dateInput", { + message <- "date must be 2024-01-01" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("date"), "2024-01-01") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("dateRangeInput", { + message <- "date_range must be 2024-01-01 to 2024-01-31" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("date_range"), c("2024-01-01", "2024-01-31")) + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("no input errors at the end", { + testthat::expect_null(app_driver$get_text(".shiny-output-error")) + }) +}) From 192c070b4857e382894bd7e72f0a1a59eedd6eb7 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 12:45:22 +0000 Subject: [PATCH 17/33] [skip style] [skip vbump] Restyle files --- tests/testthat/test-shinytest2-validate_input.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index e796e7463f..c9e5578bc3 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -138,7 +138,7 @@ testthat::describe("e2e: validate_input validates", { silent = TRUE ), try( - teal:::validate_input("selectize", function(x) identical(x, "Y"), "selectize must be Y",), + teal:::validate_input("selectize", function(x) identical(x, "Y"), "selectize must be Y", ), silent = TRUE ), try( From 19500092124699186fd0e940948a2a72fcef59af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 4 May 2026 13:47:44 +0100 Subject: [PATCH 18/33] fix: last test under input spec --- tests/testthat/test-shinytest2-validate_input.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index c9e5578bc3..d9d1e7c575 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -233,8 +233,12 @@ testthat::describe("e2e: validate_input validates", { message <- "date_range must be 2024-01-01 to 2024-01-31" testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("date_range"), c("2024-01-01", "2024-01-31")) + errors <- app_driver$get_text(".shiny-output-error") + if (is.null(errors)) { # if there are no errors, set to empty character vector to avoid testthat::expect_match error + errors <- character(0L) + } testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + testthat::expect_match(errors, message, fixed = TRUE, all = FALSE) ) }) From a505fd4d736abce1ac0388c220535b645813ce03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 4 May 2026 13:51:10 +0100 Subject: [PATCH 19/33] chore: fix long line --- tests/testthat/test-shinytest2-validate_input.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index d9d1e7c575..d371873d17 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -115,6 +115,7 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu }) testthat::describe("e2e: validate_input validates", { + skip_if_too_deep(5) all_inputs_mod <- module( label = "all inputs", ui = function(id) { @@ -234,7 +235,7 @@ testthat::describe("e2e: validate_input validates", { testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("date_range"), c("2024-01-01", "2024-01-31")) errors <- app_driver$get_text(".shiny-output-error") - if (is.null(errors)) { # if there are no errors, set to empty character vector to avoid testthat::expect_match error + if (is.null(errors)) { # Set to empty character vector to avoid testthat::expect_match error errors <- character(0L) } testthat::expect_failure( From a4ba5d0df24f78a6d001204f1314fbb08fd29f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 4 May 2026 13:55:00 +0100 Subject: [PATCH 20/33] chore: newline at the end of the file --- inst/css/validation.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/css/validation.css b/inst/css/validation.css index 9b96617db2..ae90bf1522 100644 --- a/inst/css/validation.css +++ b/inst/css/validation.css @@ -46,4 +46,4 @@ background-color: rgba(223, 70, 97, 0.1); border: 1px solid red; padding: 1em; -} \ No newline at end of file +} From b3a0af14aed1b12d73ed0d70d56493b907312181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 4 May 2026 14:02:46 +0100 Subject: [PATCH 21/33] chore: uses withr to defer app stop --- .../testthat/test-shinytest2-validate_input.R | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index d371873d17..fc39612a8f 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -30,26 +30,25 @@ testthat::test_that("e2e: validate_input displays validation message when input ) } - app <- TealAppDriver$new( + app_driver <- TealAppDriver$new( init( data = simple_teal_data(), modules = validation_module(label = "Validation Test") ) ) + withr::defer(app_driver$stop()) # Check that validation error is displayed in the output - error_text <- app$get_text(".shiny-output-error-validation") + error_text <- app_driver$get_text(".shiny-output-error-validation") testthat::expect_match(error_text, "Please select a number greater than 5") # Update input to valid value - app$set_input(app$namespaces()$module("number"), 7) - app$wait_for_idle() + app_driver$set_input(app_driver$namespaces()$module("number"), 7) + app_driver$wait_for_idle() # Check that validation error is gone - testthat::expect_null(app$get_text(".shiny-output-error-validation")) - - app$stop() + testthat::expect_null(app_driver$get_text(".shiny-output-error-validation")) }) testthat::test_that("e2e: validate_input validates many inputs (and linked output) when they return invalid value", { @@ -82,36 +81,35 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu ) } - app <- TealAppDriver$new( + app_driver <- TealAppDriver$new( init( data = simple_teal_data(), modules = multi_validation_module(label = "Multi Validation") ) ) + withr::defer(app_driver$stop()) # Initially input2 is invalid (value 5 < 10) - error_text <- app$get_text(".shiny-output-error-validation") + error_text <- app_driver$get_text(".shiny-output-error-validation") testthat::expect_match(error_text, "x and y must be identical") testthat::expect_match( - app$get_text(app$namespaces(TRUE)$module("result")), + app_driver$get_text(app_driver$namespaces(TRUE)$module("result")), "x and y must be identical" ) # Update input2 to valid value - app$set_input(app$namespaces()$module("input1"), 5) - app$set_input(app$namespaces()$module("input2"), 5) - app$wait_for_idle() + app_driver$set_input(app_driver$namespaces()$module("input1"), 5) + app_driver$set_input(app_driver$namespaces()$module("input2"), 5) + app_driver$wait_for_idle() # Check that validation passes and result is shown - error_text <- app$get_text(".shiny-output-error-validation") + error_text <- app_driver$get_text(".shiny-output-error-validation") testthat::expect_null(error_text) testthat::expect_match( - app$get_text(app$namespaces(TRUE)$module("result")), + app_driver$get_text(app_driver$namespaces(TRUE)$module("result")), "Valid inputs: 5 5" ) - - app$stop() }) testthat::describe("e2e: validate_input validates", { From 406f79c2ff45941a3ee5118158efcd5f3320964b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 4 May 2026 14:08:39 +0100 Subject: [PATCH 22/33] chore: minor correction on existing test --- tests/testthat/test-shinytest2-validate_input.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index fc39612a8f..b485624aa2 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -73,8 +73,8 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu ui = function(id) { ns <- NS(id) tagList( - numericInput(ns("input1"), "Enter y:", value = 4, min = 0, max = 100), - numericInput(ns("input2"), "Enter y:", value = 5, min = 0, max = 100), + numericInput(ns("input1"), "Enter x:", value = 4, min = 0, max = 100), + numericInput(ns("input2"), "Enter y:", value = 6, min = 0, max = 100), textOutput(ns("result")) ) } From 35d3abebd69290f1297368d1479412d18d4984ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 4 May 2026 14:14:51 +0100 Subject: [PATCH 23/33] fix: long line --- tests/testthat/test-shinytest2-validate_input.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index b485624aa2..b6e49e85f1 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -145,7 +145,11 @@ testthat::describe("e2e: validate_input validates", { silent = TRUE ), try( - teal:::validate_input("checkbox_group", function(x) identical(x, c("Check 2")), "checkbox_group must be Check 2"), + teal:::validate_input( + "checkbox_group", + function(x) identical(x, c("Check 2")), + "checkbox_group must be Check 2" + ), silent = TRUE ), try( From cf4d5d88442a525d2430dfcb9df12ba510462fa8 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 13:40:16 +0000 Subject: [PATCH 24/33] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- DESCRIPTION | 2 +- man/TealAppDriver.Rd | 947 ++++++++++++++++--------------- man/TealReportCard.Rd | 206 +++---- man/dot-teal_favicon.Rd | 4 - man/reporter_previewer_module.Rd | 6 +- man/teal-package.Rd | 1 + 6 files changed, 594 insertions(+), 572 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8734c972f3..a1a32c870a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -97,7 +97,6 @@ Encoding: UTF-8 Language: en-US LazyData: true Roxygen: list(markdown = TRUE, packages = c("roxy.shinylive")) -RoxygenNote: 7.3.3 Collate: 'TealAppDriver.R' 'after.R' @@ -134,3 +133,4 @@ Collate: 'validate_inputs.R' 'validations.R' 'zzz.R' +Config/roxygen2/version: 8.0.0 diff --git a/man/TealAppDriver.Rd b/man/TealAppDriver.Rd index e28d642baa..a9e5ba322c 100644 --- a/man/TealAppDriver.Rd +++ b/man/TealAppDriver.Rd @@ -4,11 +4,6 @@ \alias{TealAppDriver} \title{Drive a \code{teal} application} \description{ -Drive a \code{teal} application - -Drive a \code{teal} application -} -\details{ Extension of the \code{shinytest2::AppDriver} class with methods for driving a teal application for performing interactions for \code{shinytest2} tests. } @@ -18,647 +13,677 @@ driving a teal application for performing interactions for \code{shinytest2} tes } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-TealAppDriver-new}{\code{TealAppDriver$new()}} -\item \href{#method-TealAppDriver-stop}{\code{TealAppDriver$stop()}} -\item \href{#method-TealAppDriver-click}{\code{TealAppDriver$click()}} -\item \href{#method-TealAppDriver-expect_no_shiny_error}{\code{TealAppDriver$expect_no_shiny_error()}} -\item \href{#method-TealAppDriver-expect_no_validation_error}{\code{TealAppDriver$expect_no_validation_error()}} -\item \href{#method-TealAppDriver-expect_validation_error}{\code{TealAppDriver$expect_validation_error()}} -\item \href{#method-TealAppDriver-set_input}{\code{TealAppDriver$set_input()}} -\item \href{#method-TealAppDriver-navigate_teal_tab}{\code{TealAppDriver$navigate_teal_tab()}} -\item \href{#method-TealAppDriver-namespaces}{\code{TealAppDriver$namespaces()}} -\item \href{#method-TealAppDriver-get_active_module_input}{\code{TealAppDriver$get_active_module_input()}} -\item \href{#method-TealAppDriver-get_active_module_output}{\code{TealAppDriver$get_active_module_output()}} -\item \href{#method-TealAppDriver-get_active_module_table_output}{\code{TealAppDriver$get_active_module_table_output()}} -\item \href{#method-TealAppDriver-get_active_module_plot_output}{\code{TealAppDriver$get_active_module_plot_output()}} -\item \href{#method-TealAppDriver-set_active_module_input}{\code{TealAppDriver$set_active_module_input()}} -\item \href{#method-TealAppDriver-get_active_filter_vars}{\code{TealAppDriver$get_active_filter_vars()}} -\item \href{#method-TealAppDriver-get_active_data_summary_table}{\code{TealAppDriver$get_active_data_summary_table()}} -\item \href{#method-TealAppDriver-is_visible}{\code{TealAppDriver$is_visible()}} -\item \href{#method-TealAppDriver-expect_visible}{\code{TealAppDriver$expect_visible()}} -\item \href{#method-TealAppDriver-expect_hidden}{\code{TealAppDriver$expect_hidden()}} -\item \href{#method-TealAppDriver-get_active_data_filters}{\code{TealAppDriver$get_active_data_filters()}} -\item \href{#method-TealAppDriver-add_filter_var}{\code{TealAppDriver$add_filter_var()}} -\item \href{#method-TealAppDriver-remove_filter_var}{\code{TealAppDriver$remove_filter_var()}} -\item \href{#method-TealAppDriver-set_active_filter_selection}{\code{TealAppDriver$set_active_filter_selection()}} -\item \href{#method-TealAppDriver-get_attr}{\code{TealAppDriver$get_attr()}} -\item \href{#method-TealAppDriver-get_html_rvest}{\code{TealAppDriver$get_html_rvest()}} -\item \href{#method-TealAppDriver-open_url}{\code{TealAppDriver$open_url()}} -\item \href{#method-TealAppDriver-wait_for_active_module_value}{\code{TealAppDriver$wait_for_active_module_value()}} -} -} -\if{html}{\out{ -
Inherited methods + \itemize{ + \item \href{#method-TealAppDriver-initialize}{\code{TealAppDriver$new()}} + \item \href{#method-TealAppDriver-stop}{\code{TealAppDriver$stop()}} + \item \href{#method-TealAppDriver-click}{\code{TealAppDriver$click()}} + \item \href{#method-TealAppDriver-expect_no_shiny_error}{\code{TealAppDriver$expect_no_shiny_error()}} + \item \href{#method-TealAppDriver-expect_no_validation_error}{\code{TealAppDriver$expect_no_validation_error()}} + \item \href{#method-TealAppDriver-expect_validation_error}{\code{TealAppDriver$expect_validation_error()}} + \item \href{#method-TealAppDriver-set_input}{\code{TealAppDriver$set_input()}} + \item \href{#method-TealAppDriver-navigate_teal_tab}{\code{TealAppDriver$navigate_teal_tab()}} + \item \href{#method-TealAppDriver-namespaces}{\code{TealAppDriver$namespaces()}} + \item \href{#method-TealAppDriver-get_active_module_input}{\code{TealAppDriver$get_active_module_input()}} + \item \href{#method-TealAppDriver-get_active_module_output}{\code{TealAppDriver$get_active_module_output()}} + \item \href{#method-TealAppDriver-get_active_module_table_output}{\code{TealAppDriver$get_active_module_table_output()}} + \item \href{#method-TealAppDriver-get_active_module_plot_output}{\code{TealAppDriver$get_active_module_plot_output()}} + \item \href{#method-TealAppDriver-set_active_module_input}{\code{TealAppDriver$set_active_module_input()}} + \item \href{#method-TealAppDriver-get_active_filter_vars}{\code{TealAppDriver$get_active_filter_vars()}} + \item \href{#method-TealAppDriver-get_active_data_summary_table}{\code{TealAppDriver$get_active_data_summary_table()}} + \item \href{#method-TealAppDriver-is_visible}{\code{TealAppDriver$is_visible()}} + \item \href{#method-TealAppDriver-expect_visible}{\code{TealAppDriver$expect_visible()}} + \item \href{#method-TealAppDriver-expect_hidden}{\code{TealAppDriver$expect_hidden()}} + \item \href{#method-TealAppDriver-get_active_data_filters}{\code{TealAppDriver$get_active_data_filters()}} + \item \href{#method-TealAppDriver-add_filter_var}{\code{TealAppDriver$add_filter_var()}} + \item \href{#method-TealAppDriver-remove_filter_var}{\code{TealAppDriver$remove_filter_var()}} + \item \href{#method-TealAppDriver-set_active_filter_selection}{\code{TealAppDriver$set_active_filter_selection()}} + \item \href{#method-TealAppDriver-get_attr}{\code{TealAppDriver$get_attr()}} + \item \href{#method-TealAppDriver-get_html_rvest}{\code{TealAppDriver$get_html_rvest()}} + \item \href{#method-TealAppDriver-open_url}{\code{TealAppDriver$open_url()}} + \item \href{#method-TealAppDriver-wait_for_active_module_value}{\code{TealAppDriver$wait_for_active_module_value()}} + } +} +\if{html}{\out{
Inherited methods -
-}} +
}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-TealAppDriver-new}{}}} -\subsection{Method \code{new()}}{ -Initialize a \code{TealAppDriver} object for testing a \code{teal} application. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$new( +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TealAppDriver-initialize}{}}} +\subsection{\code{TealAppDriver$new()}}{ + Initialize a \code{TealAppDriver} object for testing a \code{teal} application. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$new( app, options = list(), timeout = rlang::missing_arg(), load_timeout = rlang::missing_arg(), ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{app}}{(\code{teal_app})} - -\item{\code{options}}{(\code{list}) passed to \code{shinyApp(options)}. See \code{\link[shiny:shinyApp]{shiny::shinyApp()}}.} - -\item{\code{timeout}}{(\code{numeric}) Default number of milliseconds for any timeout or +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{app}}{(\code{teal_app})} + \item{\code{options}}{(\code{list}) passed to \code{shinyApp(options)}. See \code{\link[shiny:shinyApp]{shiny::shinyApp()}}.} + \item{\code{timeout}}{(\code{numeric}) Default number of milliseconds for any timeout or timeout_ parameter in the \code{TealAppDriver} class. Defaults to 20s. See \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{new} method for more details on how to change it via options or environment variables.} - -\item{\code{load_timeout}}{(\code{numeric}) How long to wait for the app to load, in ms. + \item{\code{load_timeout}}{(\code{numeric}) How long to wait for the app to load, in ms. This includes the time to start R. Defaults to 100s. See \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{new} method for more details on how to change it via options or environment variables} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$new}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -Object of class \code{TealAppDriver} -} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$new}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Object of class \code{TealAppDriver} + } } + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-stop}{}}} -\subsection{Method \code{stop()}}{ -Extension of the parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{stop} method that prints the logs +\subsection{\code{TealAppDriver$stop()}}{ + Extension of the parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{stop} method that prints the logs if the \code{ACTIONS_STEP_DEBUG} environment variable is set to \code{true} (case of value is ignored). -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$stop(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$stop(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-click}{}}} -\subsection{Method \code{click()}}{ -Append parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click} method with a call to \code{waif_for_idle()} method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$click(...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$click()}}{ + Append parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click} method with a call to \code{waif_for_idle()} method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$click(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_no_shiny_error}{}}} -\subsection{Method \code{expect_no_shiny_error()}}{ -Check if the app has shiny errors. This checks for global shiny errors. +\subsection{\code{TealAppDriver$expect_no_shiny_error()}}{ + Check if the app has shiny errors. This checks for global shiny errors. Note that any shiny errors dependent on shiny server render will only be captured after the teal module tab is visited because shiny will not trigger server computations when the tab is invisible. So, navigate to the module tab you want to test before calling this function. Although, this catches errors hidden in the other module tabs if they are already rendered. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_no_shiny_error()}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_no_shiny_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_no_validation_error}{}}} -\subsection{Method \code{expect_no_validation_error()}}{ -Check if the app has no validation errors. This checks for global shiny validation errors. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_no_validation_error()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$expect_no_validation_error()}}{ + Check if the app has no validation errors. This checks for global shiny validation errors. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_no_validation_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_validation_error}{}}} -\subsection{Method \code{expect_validation_error()}}{ -Check if the app has validation errors. This checks for global shiny validation errors. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_validation_error()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$expect_validation_error()}}{ + Check if the app has validation errors. This checks for global shiny validation errors. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_validation_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_input}{}}} -\subsection{Method \code{set_input()}}{ -Set the input in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_input(input_id, value, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$set_input()}}{ + Set the input in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_input(input_id, value, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id with it's complete name space.} + \item{\code{value}}{The value to set the input to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id with it's complete name space.} - -\item{\code{value}}{The value to set the input to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-navigate_teal_tab}{}}} -\subsection{Method \code{navigate_teal_tab()}}{ -Navigate the teal tabs in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$navigate_teal_tab(tab)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{tab}}{(character) Labels of tabs to navigate to. +\subsection{\code{TealAppDriver$navigate_teal_tab()}}{ + Navigate the teal tabs in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$navigate_teal_tab(tab)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{tab}}{(character) Labels of tabs to navigate to. Note: Make sure to provide unique labels for the tabs.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-namespaces}{}}} -\subsection{Method \code{namespaces()}}{ -\code{NS} in different sections of \code{teal} app -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$namespaces(is_selector = FALSE)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$namespaces()}}{ + \code{NS} in different sections of \code{teal} app + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$namespaces(is_selector = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{is_selector}}{(\code{logical(1)}) whether \code{ns} function should prefix with \verb{#}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + list of \code{ns}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{is_selector}}{(\code{logical(1)}) whether \code{ns} function should prefix with \verb{#}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -list of \code{ns}. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_input}{}}} -\subsection{Method \code{get_active_module_input()}}{ -Get the input from the module in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_input()}}{ + Get the input from the module in the \code{teal} app. This function will only access inputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_input(input_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_input(input_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id to get the value from.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The value of the shiny input. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id to get the value from.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The value of the shiny input. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_output}{}}} -\subsection{Method \code{get_active_module_output()}}{ -Get the output from the module in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_output()}}{ + Get the output from the module in the \code{teal} app. This function will only access outputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_output(output_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_output(output_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{output_id}}{(character) The shiny output id to get the value from.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The value of the shiny output. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{output_id}}{(character) The shiny output id to get the value from.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The value of the shiny output. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_table_output}{}}} -\subsection{Method \code{get_active_module_table_output()}}{ -Get the output from the module's \code{teal.widgets::table_with_settings} or \code{DT::DTOutput} in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_table_output()}}{ + Get the output from the module's \code{teal.widgets::table_with_settings} or \code{DT::DTOutput} in the \code{teal} app. This function will only access outputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_table_output(table_id, which = 1)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{table_id}}{(\code{character(1)}) The id of the table in the active teal module's name space.} - -\item{\code{which}}{(integer) If there is more than one table, which should be extracted. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_table_output(table_id, which = 1)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{table_id}}{(\code{character(1)}) The id of the table in the active teal module's name space.} + \item{\code{which}}{(integer) If there is more than one table, which should be extracted. By default it will look for a table that is built using \code{teal.widgets::table_with_settings}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The data.frame with table contents. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The data.frame with table contents. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_plot_output}{}}} -\subsection{Method \code{get_active_module_plot_output()}}{ -Get the output from the module's \code{teal.widgets::plot_with_settings} in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_plot_output()}}{ + Get the output from the module's \code{teal.widgets::plot_with_settings} in the \code{teal} app. This function will only access plots from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_plot_output(plot_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_plot_output(plot_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{plot_id}}{(\code{character(1)}) The id of the plot in the active teal module's name space.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{src} attribute as \code{character(1)} vector. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{plot_id}}{(\code{character(1)}) The id of the plot in the active teal module's name space.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{src} attribute as \code{character(1)} vector. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_active_module_input}{}}} -\subsection{Method \code{set_active_module_input()}}{ -Set the input in the module in the \code{teal} app. +\subsection{\code{TealAppDriver$set_active_module_input()}}{ + Set the input in the module in the \code{teal} app. This function will only set inputs in the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_active_module_input(input_id, value, ...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_active_module_input(input_id, value, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id to get the value from.} + \item{\code{value}}{The value to set the input to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id to get the value from.} - -\item{\code{value}}{The value to set the input to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_filter_vars}{}}} -\subsection{Method \code{get_active_filter_vars()}}{ -Get the active datasets that can be accessed via the filter panel of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_filter_vars()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_active_filter_vars()}}{ + Get the active datasets that can be accessed via the filter panel of the current active teal module. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_filter_vars()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_data_summary_table}{}}} -\subsection{Method \code{get_active_data_summary_table()}}{ -Get the active data summary table -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_data_summary_table()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_active_data_summary_table()}}{ + Get the active data summary table + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_data_summary_table()} + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{data.frame} + } } -\subsection{Returns}{ -\code{data.frame} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-is_visible}{}}} -\subsection{Method \code{is_visible()}}{ -Test if \code{DOM} elements are visible on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$is_visible( +\subsection{\code{TealAppDriver$is_visible()}}{ + Test if \code{DOM} elements are visible on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$is_visible( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. A \code{CSS} id will return only one element if the UI is well formed.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Logical vector with all occurrences of the selector. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -Logical vector with all occurrences of the selector. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_visible}{}}} -\subsection{Method \code{expect_visible()}}{ -Expect that \code{DOM} elements are visible on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_visible( +\subsection{\code{TealAppDriver$expect_visible()}}{ + Expect that \code{DOM} elements are visible on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_visible( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE, timeout -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. if more than one element is found, at least one must be visible for this expectation to be successful.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - -\item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. + \item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. Defaults to the \code{timeout} parameter set during initialization of the \code{TealAppDriver} object.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_hidden}{}}} -\subsection{Method \code{expect_hidden()}}{ -Expect that \code{DOM} elements are hidden on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_hidden( +\subsection{\code{TealAppDriver$expect_hidden()}}{ + Expect that \code{DOM} elements are hidden on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_hidden( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE, timeout -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. if more than one element is found, all of them must be invisible for this expectation to be successful.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - -\item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. + \item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. Defaults to the \code{timeout} parameter set during initialization of the \code{TealAppDriver} object.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_data_filters}{}}} -\subsection{Method \code{get_active_data_filters()}}{ -Get the active filter variables from a dataset in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_data_filters(dataset_name = NULL)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to get the filter variables from. +\subsection{\code{TealAppDriver$get_active_data_filters()}}{ + Get the active filter variables from a dataset in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_data_filters(dataset_name = NULL)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to get the filter variables from. If \code{NULL}, the filter variables for all the datasets will be returned in a list.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-add_filter_var}{}}} -\subsection{Method \code{add_filter_var()}}{ -Add a new variable from the dataset to be filtered. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$add_filter_var(dataset_name, var_name, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$add_filter_var()}}{ + Add a new variable from the dataset to be filtered. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$add_filter_var(dataset_name, var_name, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to add the filter variable to.} + \item{\code{var_name}}{(character) The name of the variable to add to the filter panel.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to add the filter variable to.} - -\item{\code{var_name}}{(character) The name of the variable to add to the filter panel.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-remove_filter_var}{}}} -\subsection{Method \code{remove_filter_var()}}{ -Remove an active filter variable of a dataset from the active filter variables panel. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$remove_filter_var(dataset_name = NULL, var_name = NULL)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to remove the filter variable from. +\subsection{\code{TealAppDriver$remove_filter_var()}}{ + Remove an active filter variable of a dataset from the active filter variables panel. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$remove_filter_var(dataset_name = NULL, var_name = NULL)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to remove the filter variable from. If \code{NULL}, all the filter variables will be removed.} - -\item{\code{var_name}}{(character) The name of the variable to remove from the filter panel. + \item{\code{var_name}}{(character) The name of the variable to remove from the filter panel. If \code{NULL}, all the filter variables of the dataset will be removed.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_active_filter_selection}{}}} -\subsection{Method \code{set_active_filter_selection()}}{ -Set the active filter values for a variable of a dataset in the active filter variable panel. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_active_filter_selection(dataset_name, var_name, input, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$set_active_filter_selection()}}{ + Set the active filter values for a variable of a dataset in the active filter variable panel. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_active_filter_selection(dataset_name, var_name, input, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to set the filter value for.} + \item{\code{var_name}}{(character) The name of the variable to set the filter value for.} + \item{\code{input}}{The value to set the filter to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to set the filter value for.} - -\item{\code{var_name}}{(character) The name of the variable to set the filter value for.} - -\item{\code{input}}{The value to set the filter to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_attr}{}}} -\subsection{Method \code{get_attr()}}{ -Extract \code{html} attribute (found by a \code{selector}). -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_attr(selector, attribute)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_attr()}}{ + Extract \code{html} attribute (found by a \code{selector}). + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_attr(selector, attribute)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) specifying the selector to be used to get the content of a specific node.} + \item{\code{attribute}}{(\code{character(1)}) name of an attribute to retrieve from a node specified by \code{selector}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{character} vector. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) specifying the selector to be used to get the content of a specific node.} - -\item{\code{attribute}}{(\code{character(1)}) name of an attribute to retrieve from a node specified by \code{selector}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{character} vector. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_html_rvest}{}}} -\subsection{Method \code{get_html_rvest()}}{ -Wrapper around \code{get_html} that passes the output directly to \code{rvest::read_html}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_html_rvest(selector)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{\code{(character(1))} passed to \code{get_html}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -An XML document. +\subsection{\code{TealAppDriver$get_html_rvest()}}{ + Wrapper around \code{get_html} that passes the output directly to \code{rvest::read_html}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_html_rvest(selector)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{\code{(character(1))} passed to \code{get_html}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + An XML document. Wrapper around \code{get_url()} method that opens the app in the browser. + } } -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-open_url}{}}} -\subsection{Method \code{open_url()}}{ -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$open_url()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$open_url()}}{ + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$open_url()} + \if{html}{\out{
}} + } + \subsection{Returns}{ + Nothing. Opens the underlying teal app in the browser. + } } -\subsection{Returns}{ -Nothing. Opens the underlying teal app in the browser. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-wait_for_active_module_value}{}}} -\subsection{Method \code{wait_for_active_module_value()}}{ -Waits until a specified input, output, or export value. +\subsection{\code{TealAppDriver$wait_for_active_module_value()}}{ + Waits until a specified input, output, or export value. This function serves as a wrapper around the \code{wait_for_value} method, providing a more flexible interface for waiting on different types of values within the active module namespace. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$wait_for_active_module_value( + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$wait_for_active_module_value( input = rlang::missing_arg(), output = rlang::missing_arg(), export = rlang::missing_arg(), ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input, output, export}}{A name of an input, output, or export value. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input, output, export}}{A name of an input, output, or export value. Only one of these parameters may be used.} - -\item{\code{...}}{Must be empty. Allows for parameter expansion. + \item{\code{...}}{Must be empty. Allows for parameter expansion. Parameter with additional value to passed in \code{wait_for_value}.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + } diff --git a/man/TealReportCard.Rd b/man/TealReportCard.Rd index 8067c8002b..c7aee7a256 100644 --- a/man/TealReportCard.Rd +++ b/man/TealReportCard.Rd @@ -13,7 +13,7 @@ meta data. \examples{ ## ------------------------------------------------ -## Method `TealReportCard$append_src` +## Method `TealReportCard$append_src()` ## ------------------------------------------------ card <- TealReportCard$new()$append_src( @@ -22,7 +22,7 @@ card <- TealReportCard$new()$append_src( card$get_content()[[1]] ## ------------------------------------------------ -## Method `TealReportCard$append_encodings` +## Method `TealReportCard$append_encodings()` ## ------------------------------------------------ card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) @@ -34,137 +34,137 @@ card$get_content()[[1]] } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-TealReportCard-append_src}{\code{TealReportCard$append_src()}} -\item \href{#method-TealReportCard-append_fs}{\code{TealReportCard$append_fs()}} -\item \href{#method-TealReportCard-append_encodings}{\code{TealReportCard$append_encodings()}} -\item \href{#method-TealReportCard-clone}{\code{TealReportCard$clone()}} -} -} -\if{html}{\out{ -
Inherited methods + \itemize{ + \item \href{#method-TealReportCard-append_src}{\code{TealReportCard$append_src()}} + \item \href{#method-TealReportCard-append_fs}{\code{TealReportCard$append_fs()}} + \item \href{#method-TealReportCard-append_encodings}{\code{TealReportCard$append_encodings()}} + \item \href{#method-TealReportCard-clone}{\code{TealReportCard$clone()}} + } +} +\if{html}{\out{
Inherited methods -
-}} +
}} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_src}{}}} -\subsection{Method \code{append_src()}}{ -Appends the source code to the \code{content} meta data of this \code{TealReportCard}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_src(src, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{src}}{(\code{character(1)}) code as text.} - -\item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. +\subsection{\code{TealReportCard$append_src()}}{ + Appends the source code to the \code{content} meta data of this \code{TealReportCard}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_src(src, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{src}}{(\code{character(1)}) code as text.} + \item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. But \code{eval} parameter is always set to \code{FALSE}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -Object of class \code{TealReportCard}, invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{card <- TealReportCard$new()$append_src( + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Object of class \code{TealReportCard}, invisibly. + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{card <- TealReportCard$new()$append_src( "plot(iris)" ) card$get_content()[[1]] } -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_fs}{}}} -\subsection{Method \code{append_fs()}}{ -Appends the filter state list to the \code{content} and \code{metadata} of this \code{TealReportCard}. +\subsection{\code{TealReportCard$append_fs()}}{ + Appends the filter state list to the \code{content} and \code{metadata} of this \code{TealReportCard}. If the filter state list has an attribute named \code{formatted}, it appends it to the card otherwise it uses the default \code{yaml::as.yaml} to format the list. If the filter state list is empty, nothing is appended to the \code{content}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_fs(fs)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_fs(fs)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{fs}}{(\code{teal_slices}) object returned from \code{\link[=teal_slices]{teal_slices()}} function.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{self}, invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{fs}}{(\code{teal_slices}) object returned from \code{\link[=teal_slices]{teal_slices()}} function.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{self}, invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_encodings}{}}} -\subsection{Method \code{append_encodings()}}{ -Appends the encodings list to the \code{content} and \code{metadata} of this \code{TealReportCard}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_encodings(encodings)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{self}, invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) +\subsection{\code{TealReportCard$append_encodings()}}{ + Appends the encodings list to the \code{content} and \code{metadata} of this \code{TealReportCard}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_encodings(encodings)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{self}, invisibly. + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) card$get_content()[[1]] - } -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{TealReportCard$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/dot-teal_favicon.Rd b/man/dot-teal_favicon.Rd index 9f20d66d8c..5c55019bfd 100644 --- a/man/dot-teal_favicon.Rd +++ b/man/dot-teal_favicon.Rd @@ -1,12 +1,8 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R -\docType{data} \name{.teal_favicon} \alias{.teal_favicon} \title{The default favicon for the teal app.} -\format{ -An object of class \code{character} of length 1. -} \usage{ .teal_favicon } diff --git a/man/reporter_previewer_module.Rd b/man/reporter_previewer_module.Rd index 6f0ff86354..202a7838f7 100644 --- a/man/reporter_previewer_module.Rd +++ b/man/reporter_previewer_module.Rd @@ -24,8 +24,8 @@ It is now deprecated in favor of the options: \itemize{ \item \code{teal.reporter.nav_buttons = c("preview", "download", "load", "reset")} to control which buttons will be displayed in the drop-down. -\item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} -\item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} -\item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} } } diff --git a/man/teal-package.Rd b/man/teal-package.Rd index 49f09ba63c..27e798d529 100644 --- a/man/teal-package.Rd +++ b/man/teal-package.Rd @@ -27,6 +27,7 @@ Useful links: Authors: \itemize{ + \item Dawid Kaledkowski \email{dawid.kaledkowski@roche.com} (\href{https://orcid.org/0000-0001-9533-457X}{ORCID}) \item Pawel Rucki \email{pawel.rucki@roche.com} \item Aleksander Chlebowski \email{aleksander.chlebowski@contractors.roche.com} (\href{https://orcid.org/0000-0001-5018-6294}{ORCID}) \item Andre Verissimo \email{andre.verissimo@roche.com} (\href{https://orcid.org/0000-0002-2212-339X}{ORCID}) From de9525e649ea9a6a6602671a0304ffbbb805d33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 4 May 2026 15:45:49 +0100 Subject: [PATCH 25/33] empty: trigger ci From 490c207f3ec8bfac1cd03b0a604b0cddff81c987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 11 May 2026 14:21:10 +0100 Subject: [PATCH 26/33] fix: solves initialization problem --- inst/js/input-validator.js | 76 ++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/inst/js/input-validator.js b/inst/js/input-validator.js index 0a9760d278..f284b3bd58 100644 --- a/inst/js/input-validator.js +++ b/inst/js/input-validator.js @@ -1,29 +1,67 @@ $(document).on('shiny:connected', function () { - Shiny.addCustomMessageHandler('validateInput', function (data) { - var inputId = data.inputId; - var isValid = data.isValid; - var message = data.message; - // Try both CSS selector patterns - var selector1 = '.shiny-input-container#' + inputId; - var selector2 = '.shiny-input-container:has(#' + inputId + ')'; + /** + * Find the .shiny-input-container for a given inputId, + * then call `callback(container)` once it exists in the DOM. + * Gives up after `timeoutMs` milliseconds. + */ + function withContainer(inputId, callback, timeoutMs) { + timeoutMs = timeoutMs || 5000; - var container = $(selector1); - if (container.length === 0) { - container = $(selector2); + function findContainer() { + var container = $('.shiny-input-container#' + inputId); + if (container.length === 0) { + container = $('.shiny-input-container:has(#' + inputId + ')'); + } + return container.length > 0 ? container : null; } - if (container.length > 0) { - // Remove existing validation message - container.find('.shiny-output-error').remove(); + var existing = findContainer(); + if (existing) { + callback(existing); + return; + } - // Add validation message if rule failed - if (!isValid && message && message.trim() !== '') { - var validationSpan = $('').addClass('shiny-output-error').text(message); - container.append(validationSpan); + // Element not yet in DOM — observe until it appears or we time out + var observer = new MutationObserver(function () { + var found = findContainer(); + if (found) { + observer.disconnect(); + clearTimeout(timer); + callback(found); } - } else { - console.warn('Container not found for input: ' + inputId); + }); + + observer.observe(document.body, { childList: true, subtree: true }); + + var timer = setTimeout(function () { + observer.disconnect(); + console.warn('Container not found for input after timeout: ' + inputId); + }, timeoutMs); + } + + function applyValidation(container, isValid, message) { + // Remove existing validation messages from siblings of the container + container.parent().children('.shiny-input-validation-error').remove() + + // Add UI element for validation message if not valid + if (!isValid && message && message.trim() !== '') { + var validationSpan = $('') + .addClass('shiny-output-error') + .addClass('shiny-input-validation-error') + .text(message); + container.after(validationSpan); } + + // Clear validation message on next input change to avoid having stale messages + container.off('shiny:inputchanged').on('shiny:inputchanged', function () { + container.find('.shiny-input-validation-error').remove(); + }); + } + + Shiny.addCustomMessageHandler('validateInput', function (data) { + withContainer(data.inputId, function (container) { + applyValidation(container, data.isValid, data.message); + }); }); }); \ No newline at end of file From e97422fdea121fc8ed3d729bb768ce19afb8a115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 14 May 2026 12:54:21 +0100 Subject: [PATCH 27/33] fix: remove test in favor of implementation on other branch --- .../testthat/test-shinytest2-validate_input.R | 138 ------------------ 1 file changed, 138 deletions(-) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index b6e49e85f1..2b01a63c6d 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -111,141 +111,3 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu "Valid inputs: 5 5" ) }) - -testthat::describe("e2e: validate_input validates", { - skip_if_too_deep(5) - all_inputs_mod <- module( - label = "all inputs", - ui = function(id) { - ns <- NS(id) - tagList( - selectInput(ns("select"), "Select Input:", choices = c("A", "B", "C")), - selectizeInput(ns("selectize"), "Selectize Input:", choices = c("X", "Y", "Z")), - radioButtons(ns("radio"), "Radio Buttons:", choices = c("Option 1", "Option 2")), - checkboxGroupInput(ns("checkbox_group"), "Checkbox Group:", choices = c("Check 1", "Check 2")), - checkboxInput(ns("checkbox"), "Checkbox Input"), - dateInput(ns("date"), "Date Input:"), - dateRangeInput(ns("date_range"), "Date Range Input:") - ) - }, - server = function(id, data) { - moduleServer(id, function(input, output, session) { - reactive({ - validation <- list( - try( - teal:::validate_input("select", function(x) identical(x, "B"), "select must be B"), - silent = TRUE - ), - try( - teal:::validate_input("selectize", function(x) identical(x, "Y"), "selectize must be Y", ), - silent = TRUE - ), - try( - teal:::validate_input("radio", function(x) identical(x, "Option 2"), "radio must be Option 2"), - silent = TRUE - ), - try( - teal:::validate_input( - "checkbox_group", - function(x) identical(x, c("Check 2")), - "checkbox_group must be Check 2" - ), - silent = TRUE - ), - try( - teal:::validate_input("checkbox", function(x) identical(x, TRUE), "checkbox must be TRUE"), - silent = TRUE - ), - try( - teal:::validate_input("date", function(x) identical(x, as.Date("2024-01-01")), "date must be 2024-01-01"), - silent = TRUE - ), - try( - teal:::validate_input( - "date_range", - function(x) identical(x, c(as.Date("2024-01-01"), as.Date("2024-01-31"))), - "date_range must be 2024-01-01 to 2024-01-31" - ), - silent = TRUE - ) - ) - data() - }) - }) - } - ) - - app_driver <- TealAppDriver$new(init(data = simple_teal_data(), modules = all_inputs_mod)) - withr::defer(app_driver$stop()) - - it("selectInput", { - message <- "select must be B" - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - app_driver$set_input(app_driver$namespaces()$module("select"), "B") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) - }) - - it("selectizeInput", { - message <- "selectize must be Y" - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - app_driver$set_input(app_driver$namespaces()$module("selectize"), "Y") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) - }) - - it("radioButtons", { - message <- "radio must be Option 2" - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - app_driver$set_input(app_driver$namespaces()$module("radio"), "Option 2") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) - }) - - it("checkboxGroupInput", { - message <- "checkbox_group must be Check 2" - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - app_driver$set_input(app_driver$namespaces()$module("checkbox_group"), "Check 2") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) - }) - - it("checkboxInput", { - message <- "checkbox must be TRUE" - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - app_driver$set_input(app_driver$namespaces()$module("checkbox"), TRUE) - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) - }) - - it("dateInput", { - message <- "date must be 2024-01-01" - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - app_driver$set_input(app_driver$namespaces()$module("date"), "2024-01-01") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) - }) - - it("dateRangeInput", { - message <- "date_range must be 2024-01-01 to 2024-01-31" - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - app_driver$set_input(app_driver$namespaces()$module("date_range"), c("2024-01-01", "2024-01-31")) - errors <- app_driver$get_text(".shiny-output-error") - if (is.null(errors)) { # Set to empty character vector to avoid testthat::expect_match error - errors <- character(0L) - } - testthat::expect_failure( - testthat::expect_match(errors, message, fixed = TRUE, all = FALSE) - ) - }) - - it("no input errors at the end", { - testthat::expect_null(app_driver$get_text(".shiny-output-error")) - }) -}) From c6e6ee11e3ed47e6e0de307e5cd40b72125504e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 14 May 2026 12:56:46 +0100 Subject: [PATCH 28/33] restore --- .../testthat/test-shinytest2-validate_input.R | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index 2b01a63c6d..b6e49e85f1 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -111,3 +111,141 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu "Valid inputs: 5 5" ) }) + +testthat::describe("e2e: validate_input validates", { + skip_if_too_deep(5) + all_inputs_mod <- module( + label = "all inputs", + ui = function(id) { + ns <- NS(id) + tagList( + selectInput(ns("select"), "Select Input:", choices = c("A", "B", "C")), + selectizeInput(ns("selectize"), "Selectize Input:", choices = c("X", "Y", "Z")), + radioButtons(ns("radio"), "Radio Buttons:", choices = c("Option 1", "Option 2")), + checkboxGroupInput(ns("checkbox_group"), "Checkbox Group:", choices = c("Check 1", "Check 2")), + checkboxInput(ns("checkbox"), "Checkbox Input"), + dateInput(ns("date"), "Date Input:"), + dateRangeInput(ns("date_range"), "Date Range Input:") + ) + }, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + reactive({ + validation <- list( + try( + teal:::validate_input("select", function(x) identical(x, "B"), "select must be B"), + silent = TRUE + ), + try( + teal:::validate_input("selectize", function(x) identical(x, "Y"), "selectize must be Y", ), + silent = TRUE + ), + try( + teal:::validate_input("radio", function(x) identical(x, "Option 2"), "radio must be Option 2"), + silent = TRUE + ), + try( + teal:::validate_input( + "checkbox_group", + function(x) identical(x, c("Check 2")), + "checkbox_group must be Check 2" + ), + silent = TRUE + ), + try( + teal:::validate_input("checkbox", function(x) identical(x, TRUE), "checkbox must be TRUE"), + silent = TRUE + ), + try( + teal:::validate_input("date", function(x) identical(x, as.Date("2024-01-01")), "date must be 2024-01-01"), + silent = TRUE + ), + try( + teal:::validate_input( + "date_range", + function(x) identical(x, c(as.Date("2024-01-01"), as.Date("2024-01-31"))), + "date_range must be 2024-01-01 to 2024-01-31" + ), + silent = TRUE + ) + ) + data() + }) + }) + } + ) + + app_driver <- TealAppDriver$new(init(data = simple_teal_data(), modules = all_inputs_mod)) + withr::defer(app_driver$stop()) + + it("selectInput", { + message <- "select must be B" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("select"), "B") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("selectizeInput", { + message <- "selectize must be Y" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("selectize"), "Y") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("radioButtons", { + message <- "radio must be Option 2" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("radio"), "Option 2") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("checkboxGroupInput", { + message <- "checkbox_group must be Check 2" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("checkbox_group"), "Check 2") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("checkboxInput", { + message <- "checkbox must be TRUE" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("checkbox"), TRUE) + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("dateInput", { + message <- "date must be 2024-01-01" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("date"), "2024-01-01") + testthat::expect_failure( + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + ) + }) + + it("dateRangeInput", { + message <- "date_range must be 2024-01-01 to 2024-01-31" + testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) + app_driver$set_input(app_driver$namespaces()$module("date_range"), c("2024-01-01", "2024-01-31")) + errors <- app_driver$get_text(".shiny-output-error") + if (is.null(errors)) { # Set to empty character vector to avoid testthat::expect_match error + errors <- character(0L) + } + testthat::expect_failure( + testthat::expect_match(errors, message, fixed = TRUE, all = FALSE) + ) + }) + + it("no input errors at the end", { + testthat::expect_null(app_driver$get_text(".shiny-output-error")) + }) +}) From 7314f7998edac9f678a2bd51e9ff7bcbc43b93b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 18 May 2026 22:48:06 +0100 Subject: [PATCH 29/33] Improve on `validate_input` and exports it (#1713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Pull Request ### Changes description - [x] Resets message under input when it's changed to prevent outdated errors - See screencast on first comment - [x] ~Allows for multiple validation using `add` parameter _(instead of 1 by 1)_~ - ~Similar implementation to `checkmate::assert_***(..., add = collection)`~ - Needs alternate solution - [x] Exports `validate_input` function - [x] Fixes initialization problem ### Example app with some errors: - Validate `picks` with non-empty - Both inputs cannot choose the same letter ```r # pkgload::load_all("../teal.picks") pkgload::load_all("../teal") data <- within(teal_data(), letters <- data.frame(letter = head(LETTERS))) picks_letters <- picks( datasets("letters", "letters"), variables("letter", "letter"), values(selected = NULL) ) my_module <- module( label = "My Module", datanames = NULL, ui = function(id) { ns <- NS(id) teal.widgets::standard_layout( encoding = tagList( tags$h3("Letters"), teal.picks::picks_ui(ns("letters"), picks_letters), tags$h3("Letters2"), checkboxGroupInput(ns("letters2"), "Select letters:", choices = head(LETTERS), inline = TRUE), ), output = tagList( tags$h3("Sample plot"), plotOutput(ns("plot")) ) ) }, server = function(id, data) { moduleServer(id, function(input, output, session) { selectors <- list(letters = picks_letters) anl_inputs <- picks_srv(picks = selectors, data = data) validated_q <- reactive({ validate_input( "letters-values-selected", condition = length(anl_inputs$letters()$values$selected) > 0, message = "Select at least one letter." ) validate_input( c("letters-values-selected", "letters2"), condition = all(!anl_inputs$letters()$values$selected %in% input$letters2), message = "Letters in the first group should not be in the second group." ) data() }) output$plot <- renderPlot({ letters <- validated_q()$letters$letter tab <- rbind( Group1 = as.integer(letters %in% anl_inputs$letters()$values$selected), Group2 = as.integer(letters %in% input$letters2) ) colnames(tab) <- letters barplot( tab, beside = TRUE, legend.text = TRUE, main = "Selected letters per group", col = c("steelblue", "tomato") ) }) }) } ) init(data = data, modules = my_module) |> shiny::runApp() ``` --------- Signed-off-by: André Veríssimo <211358+averissimo@users.noreply.github.com> Co-authored-by: Dawid Kaledkowski --- NAMESPACE | 3 + R/TealAppDriver.R | 14 + R/module_teal.R | 2 +- R/validate_inputs.R | 172 +++- R/zzz.R | 3 + inst/js/input-validator.js | 61 +- man/TealAppDriver.Rd | 952 +++++++++--------- man/TealReportCard.Rd | 206 ++-- man/dot-teal_favicon.Rd | 4 + man/reporter_previewer_module.Rd | 6 +- man/teal-package.Rd | 1 - man/validate_input.Rd | 163 ++- tests/testthat/test-shinytest2-modules.R | 8 +- .../test-shinytest2-teal_data_module.R | 15 +- .../testthat/test-shinytest2-validate_input.R | 215 ++-- tests/testthat/test-validate_input.R | 66 ++ 16 files changed, 1204 insertions(+), 687 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index ffb68e3e30..8489332806 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -32,6 +32,7 @@ export(modify_header) export(modify_title) export(module) export(modules) +export(need_input) export(report_card_template) export(reporter_previewer_module) export(srv_session_info) @@ -43,10 +44,12 @@ export(teal_transform_module) export(ui_session_info) export(ui_teal) export(ui_transform_teal_data) +export(use_validate_input_js) export(validate_has_data) export(validate_has_elements) export(validate_has_variable) export(validate_in) +export(validate_input) export(validate_inputs) export(validate_n_levels) export(validate_no_intersection) diff --git a/R/TealAppDriver.R b/R/TealAppDriver.R index 7c0e2fd794..ee35595920 100644 --- a/R/TealAppDriver.R +++ b/R/TealAppDriver.R @@ -38,16 +38,21 @@ TealAppDriver <- R6::R6Class( # nolint: object_name. #' #' See [`shinytest2::AppDriver`] `new` method for more details on how to change it #' via options or environment variables + #' @param teal_options (`list`) named list of R options to set inside the spawned + #' Shiny process (e.g. `list(teal.enable_deep_linking = TRUE)`). Applied at session + #' start so option-gated server logic sees them. #' @param ... Additional arguments to be passed to `shinytest2::AppDriver$new` #' #' #' @return Object of class `TealAppDriver` initialize = function(app, options = list(), + teal_options = list(), timeout = rlang::missing_arg(), load_timeout = rlang::missing_arg(), ...) { checkmate::assert_class(app, "teal_app") + checkmate::assert_list(teal_options, names = "named") # Default timeout is hardcoded to 4s in shinytest2:::resolve_timeout # New default is set with environment variables if not already set by user # envvars have the lowest priority in shinytest2 timeout resolution. @@ -59,6 +64,15 @@ TealAppDriver <- R6::R6Class( # nolint: object_name. extra_envvar$SHINYTEST2_TIMEOUT <- "100000" } + if (length(teal_options)) { + orig_server <- app$server + opts <- teal_options + app$server <- function(input, output, session) { + do.call(base::options, opts) + orig_server(input, output, session) + } + } + suppressWarnings( withr::with_envvar( new = extra_envvar, diff --git a/R/module_teal.R b/R/module_teal.R index 8b40a47a3c..b7de560986 100644 --- a/R/module_teal.R +++ b/R/module_teal.R @@ -108,7 +108,7 @@ ui_teal <- function(id, modules) { include_teal_css_js(), shinyjs::useShinyjs(), shiny::includeScript(system.file("js/extendShinyJs.js", package = "teal")), - shiny::singleton(shiny::includeScript(system.file("js/input-validator.js", package = "teal"))), + use_validate_input_js(), shiny_busy_message_panel, tags$div(id = ns("tabpanel_wrapper"), class = "teal-body", navbar), tags$hr(style = "margin: 1rem 0 0.5rem 0;") diff --git a/R/validate_inputs.R b/R/validate_inputs.R index ec2d2e7e8d..da179fd9cd 100644 --- a/R/validate_inputs.R +++ b/R/validate_inputs.R @@ -196,19 +196,168 @@ any_names <- function(x) { #' Validate input #' +#' Validate a Shiny input and send validation messages to the client. +#' The message appears both in the input widget and in the output element +#' that calls `validate_input`. +#' @details +#' - `validate_input():` +#' +#' Validate a Shiny input and send validation messages to the client. +#' The message appears both in the input widget and in the output element +#' that calls `validate_input`. +#' #' @param inputId (`character`) Character of input ID(s) to validate #' @param condition (`logical(1)`, `function(x)`) Logical value or function returning logical value. #' Condition should determine expected state, `FALSE` throws. -#' @param message (`character(1)`) Character string of validation message to display +#' @param message (`character(1)`) Character string of validation message to display. #' @param session Shiny session object #' #' @return `NULL` or `shiny.silent.error` when condition is not met #' -#' @keywords internal +#' @examples +#' # Only run examples in interactive R sessions +#' options(device.ask.default = FALSE) +#' +#' ui <- fluidPage( +#' checkboxGroupInput("in1", "Check some letters", choices = head(LETTERS)), +#' selectizeInput("in2", "Select a state", choices = c("", state.name)), +#' use_validate_input_js(), +#' plotOutput("plot") +#' ) +#' +#' server <- function(input, output) { +#' output$plot <- renderPlot({ +#' validate_input("in1", condition = function(x) length(x) > 0, "Check at least one letter!") +#' validate_input("in2", condition = function(x) x != "", "Please choose a state.") +#' plot(1:10, main = paste(c(input$in1, input$in2), collapse = ", ")) +#' }) +#' } +#' +#' if (interactive()) { +#' shinyApp(ui, server) +#' } +#' +#' my_module <- module( +#' label = "My Module", +#' datanames = NULL, +#' ui = function(id) { +#' ns <- NS(id) +#' tagList( +#' checkboxGroupInput(ns("letters"), "Select letters:", choices = head(LETTERS), inline = TRUE), +#' checkboxGroupInput(ns("letters2"), "Select letters:", choices = head(LETTERS), inline = TRUE), +#' tags$h3("Sample plot"), +#' plotOutput(ns("plot")) +#' ) +#' }, +#' server = function(id, data) { +#' moduleServer(id, function(input, output, session) { +#' output$plot <- renderPlot({ +#' validate_input( +#' "letters", +#' condition = function(x) length(x) > 0, +#' message = "Select at least one letter." +#' ) +#' validate_input( +#' c("letters", "letters2"), +#' condition = function(x, y) all(!x %in% y), +#' message = "Letters in the first group should not be in the second group." +#' ) +#' tab <- rbind( +#' Group1 = as.integer(head(LETTERS) %in% input$letters), +#' Group2 = as.integer(head(LETTERS) %in% input$letters2) +#' ) +#' colnames(tab) <- head(LETTERS) +#' barplot( +#' tab, +#' beside = TRUE, legend.text = TRUE, main = "Selected letters per group", +#' col = c("steelblue", "tomato") +#' ) +#' }) +#' }) +#' } +#' ) +#' +#' app <- init( +#' data = within(teal_data(), iris <- iris), +#' modules = my_module +#' ) +#' +#' if (interactive()) { +#' shinyApp(app$ui, app$server) +#' } +#' @export validate_input <- function(inputId, # nolint condition = function(x) TRUE, - message = "", + message = "Check the input", session = shiny::getDefaultReactiveDomain()) { + validate(need_input(inputId, condition, message, session)) +} + +#' @rdname validate_input +#' @details +#' - `need_input()`: Validate a Shiny input and returns a message to be used in a [`shiny::validate()`] call. +#' The message is sent to the client and appears both in the input widget. +#' To observe the message in the output element, `need_input()` should be called inside a `shiny::validate()` call, e.g. via `validate(validate_input(...))`. +#' @export +#' @examples +#' my_module <- module( +#' label = "My Module", +#' datanames = NULL, +#' ui = function(id) { +#' ns <- NS(id) +#' tagList( +#' checkboxGroupInput(ns("letters1"), "Select letters:", choices = head(LETTERS), inline = TRUE), +#' checkboxGroupInput(ns("letters2"), "Select letters:", choices = head(LETTERS), inline = TRUE), +#' tags$h3("Sample plot"), +#' plotOutput(ns("plot")) +#' ) +#' }, +#' server = function(id, data) { +#' moduleServer(id, function(input, output, session) { +#' output$plot <- renderPlot({ +#' validate( +#' need_input( +#' "letters1", +#' condition = function(x) length(x) > 0, +#' message = "Select at least one letter." +#' ), +#' need_input( +#' c("letters1", "letters2"), +#' condition = function(x, y) all(!x %in% y), +#' message = "Letters in the first group should not be in the second group." +#' ) +#' ) +#' tab <- rbind( +#' Group1 = as.integer(head(LETTERS) %in% input$letters1), +#' Group2 = as.integer(head(LETTERS) %in% input$letters2) +#' ) +#' colnames(tab) <- head(LETTERS) +#' barplot( +#' tab, +#' beside = TRUE, legend.text = TRUE, main = "Selected letters per group", +#' col = c("steelblue", "tomato") +#' ) +#' }) +#' }) +#' } +#' ) +#' +#' app <- init( +#' data = within(teal_data(), iris <- iris), +#' modules = my_module +#' ) +#' +#' if (interactive()) { +#' shinyApp(app$ui, app$server) +#' } +need_input <- function(inputId, # nolint + condition = function(x) TRUE, + message = "Check the input", + session = shiny::getDefaultReactiveDomain()) { + # Simple fingerprint based on inputId and message to ensure validation messages + # in Javascript are not incorrectly removed. + fingerprint <- paste0(inputId, collapse = "|") + checkmate::assert_character(inputId, min.len = 1) checkmate::assert( checkmate::check_flag(condition), @@ -229,9 +378,22 @@ validate_input <- function(inputId, # nolint session$sendCustomMessage("validateInput", list( inputId = session$ns(id), isValid = condition_result, - message = message + message = message, + fingerprint = fingerprint )) }) - validate(need(condition_result, message)) + need(condition_result, message) +} + +#' @rdname validate_input +#' @details +#' - `use_validate_input_js()`: +#' +#' Include JavaScript for client-side input validation. +#' @export +use_validate_input_js <- function() { + addResourcePath("js", system.file("js", package = "teal")) + # system.file("js", "input-validator.js", package = "teal")) + singleton(tags$script(src = "js/input-validator.js")) } diff --git a/R/zzz.R b/R/zzz.R index 5dd0c5a2f5..6db7bca13e 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -20,6 +20,9 @@ teal.logger::register_logger("teal") teal.logger::register_handlers("teal") + if (getRversion() < "4.4") { + assign("%||%", rlang::`%||%`, envir = getNamespace(pkgname)) + } invisible() } diff --git a/inst/js/input-validator.js b/inst/js/input-validator.js index f284b3bd58..cb9eee7bf7 100644 --- a/inst/js/input-validator.js +++ b/inst/js/input-validator.js @@ -1,16 +1,12 @@ $(document).on('shiny:connected', function () { - - /** - * Find the .shiny-input-container for a given inputId, - * then call `callback(container)` once it exists in the DOM. - * Gives up after `timeoutMs` milliseconds. - */ function withContainer(inputId, callback, timeoutMs) { timeoutMs = timeoutMs || 5000; function findContainer() { + // First try direct match var container = $('.shiny-input-container#' + inputId); if (container.length === 0) { + // Then try to find container that has this input container = $('.shiny-input-container:has(#' + inputId + ')'); } return container.length > 0 ? container : null; @@ -32,7 +28,29 @@ $(document).on('shiny:connected', function () { } }); - observer.observe(document.body, { childList: true, subtree: true }); + // More efficient approach: find a relevant parent container to observe instead of the entire body + function findTargetContainer() { + // Try to find a container with an ID that partially matches inputId + var idComponents = inputId.split('-'); + + // Look for potential parent containers by checking IDs that partially match + for (var i = idComponents.length; i >= 1; i--) { + var partialId = idComponents.slice(0, i).join('-'); + var potentialContainer = $('#' + partialId); + if (potentialContainer.length > 0) { + // Return the closest shiny container or the element itself + var shinyContainer = potentialContainer.closest('.shiny-bound-input, .shiny-input-container, .form-group'); + return shinyContainer.length > 0 ? shinyContainer[0] : potentialContainer[0]; + } + } + + // Last resort: observe the body (but this should rarely happen) + return document.body; + } + + // Observe only the relevant container instead of the entire body + var targetContainer = findTargetContainer(); + observer.observe(targetContainer, { childList: true, subtree: true }); var timer = setTimeout(function () { observer.disconnect(); @@ -40,13 +58,16 @@ $(document).on('shiny:connected', function () { }, timeoutMs); } - function applyValidation(container, isValid, message) { + function applyValidation(container, inputId, isValid, message, fingerprint) { // Remove existing validation messages from siblings of the container - container.parent().children('.shiny-input-validation-error').remove() + const data_ref = `${inputId}-${fingerprint}`; + const child_selector = `.shiny-input-validation-error[data-ref="${data_ref}"]`; + container.parent().children(child_selector).remove(); // Add UI element for validation message if not valid if (!isValid && message && message.trim() !== '') { - var validationSpan = $('') + const validationSpan = $('') + .attr('data-ref', data_ref) .addClass('shiny-output-error') .addClass('shiny-input-validation-error') .text(message); @@ -54,14 +75,26 @@ $(document).on('shiny:connected', function () { } // Clear validation message on next input change to avoid having stale messages - container.off('shiny:inputchanged').on('shiny:inputchanged', function () { - container.find('.shiny-input-validation-error').remove(); + const found = container.find(`#${inputId}`); + const inputEl = found.length ? found : container; + let previousValue = inputEl.val(); + // Only remove if the value really changed, not just a re-render + inputEl.off('shiny:inputchanged').one('shiny:inputchanged', function (event) { + if (event.name !== inputEl.attr('id')) { + return; + } + // Only clear if the value actually changed + if (JSON.stringify(event.value) === JSON.stringify(previousValue)) { + return; + } + previousValue = event.value; + container.parent().children(child_selector).remove() // Remove validation message }); } Shiny.addCustomMessageHandler('validateInput', function (data) { withContainer(data.inputId, function (container) { - applyValidation(container, data.isValid, data.message); + applyValidation(container, data.inputId, data.isValid, data.message, data.fingerprint); }); }); -}); \ No newline at end of file +}); diff --git a/man/TealAppDriver.Rd b/man/TealAppDriver.Rd index a9e5ba322c..7435125985 100644 --- a/man/TealAppDriver.Rd +++ b/man/TealAppDriver.Rd @@ -4,6 +4,11 @@ \alias{TealAppDriver} \title{Drive a \code{teal} application} \description{ +Drive a \code{teal} application + +Drive a \code{teal} application +} +\details{ Extension of the \code{shinytest2::AppDriver} class with methods for driving a teal application for performing interactions for \code{shinytest2} tests. } @@ -13,677 +18,652 @@ driving a teal application for performing interactions for \code{shinytest2} tes } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-TealAppDriver-initialize}{\code{TealAppDriver$new()}} - \item \href{#method-TealAppDriver-stop}{\code{TealAppDriver$stop()}} - \item \href{#method-TealAppDriver-click}{\code{TealAppDriver$click()}} - \item \href{#method-TealAppDriver-expect_no_shiny_error}{\code{TealAppDriver$expect_no_shiny_error()}} - \item \href{#method-TealAppDriver-expect_no_validation_error}{\code{TealAppDriver$expect_no_validation_error()}} - \item \href{#method-TealAppDriver-expect_validation_error}{\code{TealAppDriver$expect_validation_error()}} - \item \href{#method-TealAppDriver-set_input}{\code{TealAppDriver$set_input()}} - \item \href{#method-TealAppDriver-navigate_teal_tab}{\code{TealAppDriver$navigate_teal_tab()}} - \item \href{#method-TealAppDriver-namespaces}{\code{TealAppDriver$namespaces()}} - \item \href{#method-TealAppDriver-get_active_module_input}{\code{TealAppDriver$get_active_module_input()}} - \item \href{#method-TealAppDriver-get_active_module_output}{\code{TealAppDriver$get_active_module_output()}} - \item \href{#method-TealAppDriver-get_active_module_table_output}{\code{TealAppDriver$get_active_module_table_output()}} - \item \href{#method-TealAppDriver-get_active_module_plot_output}{\code{TealAppDriver$get_active_module_plot_output()}} - \item \href{#method-TealAppDriver-set_active_module_input}{\code{TealAppDriver$set_active_module_input()}} - \item \href{#method-TealAppDriver-get_active_filter_vars}{\code{TealAppDriver$get_active_filter_vars()}} - \item \href{#method-TealAppDriver-get_active_data_summary_table}{\code{TealAppDriver$get_active_data_summary_table()}} - \item \href{#method-TealAppDriver-is_visible}{\code{TealAppDriver$is_visible()}} - \item \href{#method-TealAppDriver-expect_visible}{\code{TealAppDriver$expect_visible()}} - \item \href{#method-TealAppDriver-expect_hidden}{\code{TealAppDriver$expect_hidden()}} - \item \href{#method-TealAppDriver-get_active_data_filters}{\code{TealAppDriver$get_active_data_filters()}} - \item \href{#method-TealAppDriver-add_filter_var}{\code{TealAppDriver$add_filter_var()}} - \item \href{#method-TealAppDriver-remove_filter_var}{\code{TealAppDriver$remove_filter_var()}} - \item \href{#method-TealAppDriver-set_active_filter_selection}{\code{TealAppDriver$set_active_filter_selection()}} - \item \href{#method-TealAppDriver-get_attr}{\code{TealAppDriver$get_attr()}} - \item \href{#method-TealAppDriver-get_html_rvest}{\code{TealAppDriver$get_html_rvest()}} - \item \href{#method-TealAppDriver-open_url}{\code{TealAppDriver$open_url()}} - \item \href{#method-TealAppDriver-wait_for_active_module_value}{\code{TealAppDriver$wait_for_active_module_value()}} - } -} -\if{html}{\out{
Inherited methods +\itemize{ +\item \href{#method-TealAppDriver-new}{\code{TealAppDriver$new()}} +\item \href{#method-TealAppDriver-stop}{\code{TealAppDriver$stop()}} +\item \href{#method-TealAppDriver-click}{\code{TealAppDriver$click()}} +\item \href{#method-TealAppDriver-expect_no_shiny_error}{\code{TealAppDriver$expect_no_shiny_error()}} +\item \href{#method-TealAppDriver-expect_no_validation_error}{\code{TealAppDriver$expect_no_validation_error()}} +\item \href{#method-TealAppDriver-expect_validation_error}{\code{TealAppDriver$expect_validation_error()}} +\item \href{#method-TealAppDriver-set_input}{\code{TealAppDriver$set_input()}} +\item \href{#method-TealAppDriver-navigate_teal_tab}{\code{TealAppDriver$navigate_teal_tab()}} +\item \href{#method-TealAppDriver-namespaces}{\code{TealAppDriver$namespaces()}} +\item \href{#method-TealAppDriver-get_active_module_input}{\code{TealAppDriver$get_active_module_input()}} +\item \href{#method-TealAppDriver-get_active_module_output}{\code{TealAppDriver$get_active_module_output()}} +\item \href{#method-TealAppDriver-get_active_module_table_output}{\code{TealAppDriver$get_active_module_table_output()}} +\item \href{#method-TealAppDriver-get_active_module_plot_output}{\code{TealAppDriver$get_active_module_plot_output()}} +\item \href{#method-TealAppDriver-set_active_module_input}{\code{TealAppDriver$set_active_module_input()}} +\item \href{#method-TealAppDriver-get_active_filter_vars}{\code{TealAppDriver$get_active_filter_vars()}} +\item \href{#method-TealAppDriver-get_active_data_summary_table}{\code{TealAppDriver$get_active_data_summary_table()}} +\item \href{#method-TealAppDriver-is_visible}{\code{TealAppDriver$is_visible()}} +\item \href{#method-TealAppDriver-expect_visible}{\code{TealAppDriver$expect_visible()}} +\item \href{#method-TealAppDriver-expect_hidden}{\code{TealAppDriver$expect_hidden()}} +\item \href{#method-TealAppDriver-get_active_data_filters}{\code{TealAppDriver$get_active_data_filters()}} +\item \href{#method-TealAppDriver-add_filter_var}{\code{TealAppDriver$add_filter_var()}} +\item \href{#method-TealAppDriver-remove_filter_var}{\code{TealAppDriver$remove_filter_var()}} +\item \href{#method-TealAppDriver-set_active_filter_selection}{\code{TealAppDriver$set_active_filter_selection()}} +\item \href{#method-TealAppDriver-get_attr}{\code{TealAppDriver$get_attr()}} +\item \href{#method-TealAppDriver-get_html_rvest}{\code{TealAppDriver$get_html_rvest()}} +\item \href{#method-TealAppDriver-open_url}{\code{TealAppDriver$open_url()}} +\item \href{#method-TealAppDriver-wait_for_active_module_value}{\code{TealAppDriver$wait_for_active_module_value()}} +} +} +\if{html}{\out{ +
Inherited methods -
}} +
+}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-TealAppDriver-initialize}{}}} -\subsection{\code{TealAppDriver$new()}}{ - Initialize a \code{TealAppDriver} object for testing a \code{teal} application. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$new( +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TealAppDriver-new}{}}} +\subsection{Method \code{new()}}{ +Initialize a \code{TealAppDriver} object for testing a \code{teal} application. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$new( app, options = list(), + teal_options = list(), timeout = rlang::missing_arg(), load_timeout = rlang::missing_arg(), ... -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{app}}{(\code{teal_app})} - \item{\code{options}}{(\code{list}) passed to \code{shinyApp(options)}. See \code{\link[shiny:shinyApp]{shiny::shinyApp()}}.} - \item{\code{timeout}}{(\code{numeric}) Default number of milliseconds for any timeout or +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{app}}{(\code{teal_app})} + +\item{\code{options}}{(\code{list}) passed to \code{shinyApp(options)}. See \code{\link[shiny:shinyApp]{shiny::shinyApp()}}.} + +\item{\code{teal_options}}{(\code{list}) named list of R options to set inside the spawned +Shiny process (e.g. \code{list(teal.enable_deep_linking = TRUE)}). Applied at session +start so option-gated server logic sees them.} + +\item{\code{timeout}}{(\code{numeric}) Default number of milliseconds for any timeout or timeout_ parameter in the \code{TealAppDriver} class. Defaults to 20s. See \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{new} method for more details on how to change it via options or environment variables.} - \item{\code{load_timeout}}{(\code{numeric}) How long to wait for the app to load, in ms. + +\item{\code{load_timeout}}{(\code{numeric}) How long to wait for the app to load, in ms. This includes the time to start R. Defaults to 100s. See \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{new} method for more details on how to change it via options or environment variables} - \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$new}} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - Object of class \code{TealAppDriver} - } -} +\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$new}} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +Object of class \code{TealAppDriver} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-stop}{}}} -\subsection{\code{TealAppDriver$stop()}}{ - Extension of the parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{stop} method that prints the logs +\subsection{Method \code{stop()}}{ +Extension of the parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{stop} method that prints the logs if the \code{ACTIONS_STEP_DEBUG} environment variable is set to \code{true} (case of value is ignored). - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$stop(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} - } - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$stop(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-click}{}}} -\subsection{\code{TealAppDriver$click()}}{ - Append parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click} method with a call to \code{waif_for_idle()} method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$click(...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{click()}}{ +Append parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click} method with a call to \code{waif_for_idle()} method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$click(...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_no_shiny_error}{}}} -\subsection{\code{TealAppDriver$expect_no_shiny_error()}}{ - Check if the app has shiny errors. This checks for global shiny errors. +\subsection{Method \code{expect_no_shiny_error()}}{ +Check if the app has shiny errors. This checks for global shiny errors. Note that any shiny errors dependent on shiny server render will only be captured after the teal module tab is visited because shiny will not trigger server computations when the tab is invisible. So, navigate to the module tab you want to test before calling this function. Although, this catches errors hidden in the other module tabs if they are already rendered. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$expect_no_shiny_error()} - \if{html}{\out{
}} - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$expect_no_shiny_error()}\if{html}{\out{
}} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_no_validation_error}{}}} -\subsection{\code{TealAppDriver$expect_no_validation_error()}}{ - Check if the app has no validation errors. This checks for global shiny validation errors. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$expect_no_validation_error()} - \if{html}{\out{
}} - } +\subsection{Method \code{expect_no_validation_error()}}{ +Check if the app has no validation errors. This checks for global shiny validation errors. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$expect_no_validation_error()}\if{html}{\out{
}} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_validation_error}{}}} -\subsection{\code{TealAppDriver$expect_validation_error()}}{ - Check if the app has validation errors. This checks for global shiny validation errors. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$expect_validation_error()} - \if{html}{\out{
}} - } +\subsection{Method \code{expect_validation_error()}}{ +Check if the app has validation errors. This checks for global shiny validation errors. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$expect_validation_error()}\if{html}{\out{
}} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_input}{}}} -\subsection{\code{TealAppDriver$set_input()}}{ - Set the input in the \code{teal} app. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$set_input(input_id, value, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{input_id}}{(character) The shiny input id with it's complete name space.} - \item{\code{value}}{The value to set the input to.} - \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The \code{TealAppDriver} object invisibly. - } +\subsection{Method \code{set_input()}}{ +Set the input in the \code{teal} app. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$set_input(input_id, value, ...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{input_id}}{(character) The shiny input id with it's complete name space.} + +\item{\code{value}}{The value to set the input to.} + +\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The \code{TealAppDriver} object invisibly. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-navigate_teal_tab}{}}} -\subsection{\code{TealAppDriver$navigate_teal_tab()}}{ - Navigate the teal tabs in the \code{teal} app. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$navigate_teal_tab(tab)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{tab}}{(character) Labels of tabs to navigate to. -Note: Make sure to provide unique labels for the tabs.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The \code{TealAppDriver} object invisibly. - } +\subsection{Method \code{navigate_teal_tab()}}{ +Navigate the teal tabs in the \code{teal} app. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$navigate_teal_tab(tab)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{tab}}{(character) Labels of tabs to navigate to. +Note: Make sure to provide unique labels for the tabs.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The \code{TealAppDriver} object invisibly. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-namespaces}{}}} -\subsection{\code{TealAppDriver$namespaces()}}{ - \code{NS} in different sections of \code{teal} app - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$namespaces(is_selector = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{is_selector}}{(\code{logical(1)}) whether \code{ns} function should prefix with \verb{#}.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - list of \code{ns}. - } +\subsection{Method \code{namespaces()}}{ +\code{NS} in different sections of \code{teal} app +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$namespaces(is_selector = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{is_selector}}{(\code{logical(1)}) whether \code{ns} function should prefix with \verb{#}.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +list of \code{ns}. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_input}{}}} -\subsection{\code{TealAppDriver$get_active_module_input()}}{ - Get the input from the module in the \code{teal} app. +\subsection{Method \code{get_active_module_input()}}{ +Get the input from the module in the \code{teal} app. This function will only access inputs from the name space of the current active teal module. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_active_module_input(input_id)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{input_id}}{(character) The shiny input id to get the value from.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The value of the shiny input. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_input(input_id)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{input_id}}{(character) The shiny input id to get the value from.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The value of the shiny input. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_output}{}}} -\subsection{\code{TealAppDriver$get_active_module_output()}}{ - Get the output from the module in the \code{teal} app. +\subsection{Method \code{get_active_module_output()}}{ +Get the output from the module in the \code{teal} app. This function will only access outputs from the name space of the current active teal module. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_active_module_output(output_id)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{output_id}}{(character) The shiny output id to get the value from.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The value of the shiny output. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_output(output_id)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{output_id}}{(character) The shiny output id to get the value from.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The value of the shiny output. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_table_output}{}}} -\subsection{\code{TealAppDriver$get_active_module_table_output()}}{ - Get the output from the module's \code{teal.widgets::table_with_settings} or \code{DT::DTOutput} in the \code{teal} app. +\subsection{Method \code{get_active_module_table_output()}}{ +Get the output from the module's \code{teal.widgets::table_with_settings} or \code{DT::DTOutput} in the \code{teal} app. This function will only access outputs from the name space of the current active teal module. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_active_module_table_output(table_id, which = 1)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{table_id}}{(\code{character(1)}) The id of the table in the active teal module's name space.} - \item{\code{which}}{(integer) If there is more than one table, which should be extracted. -By default it will look for a table that is built using \code{teal.widgets::table_with_settings}.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The data.frame with table contents. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_table_output(table_id, which = 1)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{table_id}}{(\code{character(1)}) The id of the table in the active teal module's name space.} + +\item{\code{which}}{(integer) If there is more than one table, which should be extracted. +By default it will look for a table that is built using \code{teal.widgets::table_with_settings}.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The data.frame with table contents. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_plot_output}{}}} -\subsection{\code{TealAppDriver$get_active_module_plot_output()}}{ - Get the output from the module's \code{teal.widgets::plot_with_settings} in the \code{teal} app. +\subsection{Method \code{get_active_module_plot_output()}}{ +Get the output from the module's \code{teal.widgets::plot_with_settings} in the \code{teal} app. This function will only access plots from the name space of the current active teal module. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_active_module_plot_output(plot_id)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{plot_id}}{(\code{character(1)}) The id of the plot in the active teal module's name space.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The \code{src} attribute as \code{character(1)} vector. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_plot_output(plot_id)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{plot_id}}{(\code{character(1)}) The id of the plot in the active teal module's name space.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The \code{src} attribute as \code{character(1)} vector. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_active_module_input}{}}} -\subsection{\code{TealAppDriver$set_active_module_input()}}{ - Set the input in the module in the \code{teal} app. +\subsection{Method \code{set_active_module_input()}}{ +Set the input in the module in the \code{teal} app. This function will only set inputs in the name space of the current active teal module. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$set_active_module_input(input_id, value, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{input_id}}{(character) The shiny input id to get the value from.} - \item{\code{value}}{The value to set the input to.} - \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The \code{TealAppDriver} object invisibly. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$set_active_module_input(input_id, value, ...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{input_id}}{(character) The shiny input id to get the value from.} + +\item{\code{value}}{The value to set the input to.} + +\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The \code{TealAppDriver} object invisibly. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_filter_vars}{}}} -\subsection{\code{TealAppDriver$get_active_filter_vars()}}{ - Get the active datasets that can be accessed via the filter panel of the current active teal module. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_active_filter_vars()} - \if{html}{\out{
}} - } +\subsection{Method \code{get_active_filter_vars()}}{ +Get the active datasets that can be accessed via the filter panel of the current active teal module. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_filter_vars()}\if{html}{\out{
}} } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_data_summary_table}{}}} -\subsection{\code{TealAppDriver$get_active_data_summary_table()}}{ - Get the active data summary table - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_active_data_summary_table()} - \if{html}{\out{
}} - } - \subsection{Returns}{ - \code{data.frame} - } +\subsection{Method \code{get_active_data_summary_table()}}{ +Get the active data summary table +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_data_summary_table()}\if{html}{\out{
}} } +\subsection{Returns}{ +\code{data.frame} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-is_visible}{}}} -\subsection{\code{TealAppDriver$is_visible()}}{ - Test if \code{DOM} elements are visible on the page with a JavaScript call. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$is_visible( +\subsection{Method \code{is_visible()}}{ +Test if \code{DOM} elements are visible on the page with a JavaScript call. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$is_visible( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. A \code{CSS} id will return only one element if the UI is well formed.} - \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information + +\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - Logical vector with all occurrences of the selector. - } } - +\if{html}{\out{
}} +} +\subsection{Returns}{ +Logical vector with all occurrences of the selector. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_visible}{}}} -\subsection{\code{TealAppDriver$expect_visible()}}{ - Expect that \code{DOM} elements are visible on the page with a JavaScript call. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$expect_visible( +\subsection{Method \code{expect_visible()}}{ +Expect that \code{DOM} elements are visible on the page with a JavaScript call. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$expect_visible( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE, timeout -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. if more than one element is found, at least one must be visible for this expectation to be successful.} - \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) + +\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - \item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. + +\item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. Defaults to the \code{timeout} parameter set during initialization of the \code{TealAppDriver} object.} - } - \if{html}{\out{
}} - } } - +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_hidden}{}}} -\subsection{\code{TealAppDriver$expect_hidden()}}{ - Expect that \code{DOM} elements are hidden on the page with a JavaScript call. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$expect_hidden( +\subsection{Method \code{expect_hidden()}}{ +Expect that \code{DOM} elements are hidden on the page with a JavaScript call. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$expect_hidden( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE, timeout -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. if more than one element is found, all of them must be invisible for this expectation to be successful.} - \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) + +\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - \item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. + +\item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. Defaults to the \code{timeout} parameter set during initialization of the \code{TealAppDriver} object.} - } - \if{html}{\out{
}} - } } - +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_data_filters}{}}} -\subsection{\code{TealAppDriver$get_active_data_filters()}}{ - Get the active filter variables from a dataset in the \code{teal} app. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_active_data_filters(dataset_name = NULL)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{dataset_name}}{(character) The name of the dataset to get the filter variables from. -If \code{NULL}, the filter variables for all the datasets will be returned in a list.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{get_active_data_filters()}}{ +Get the active filter variables from a dataset in the \code{teal} app. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_data_filters(dataset_name = NULL)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{dataset_name}}{(character) The name of the dataset to get the filter variables from. +If \code{NULL}, the filter variables for all the datasets will be returned in a list.} +} +\if{html}{\out{
}} +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-add_filter_var}{}}} -\subsection{\code{TealAppDriver$add_filter_var()}}{ - Add a new variable from the dataset to be filtered. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$add_filter_var(dataset_name, var_name, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{dataset_name}}{(character) The name of the dataset to add the filter variable to.} - \item{\code{var_name}}{(character) The name of the variable to add to the filter panel.} - \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The \code{TealAppDriver} object invisibly. - } +\subsection{Method \code{add_filter_var()}}{ +Add a new variable from the dataset to be filtered. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$add_filter_var(dataset_name, var_name, ...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{dataset_name}}{(character) The name of the dataset to add the filter variable to.} + +\item{\code{var_name}}{(character) The name of the variable to add to the filter panel.} + +\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The \code{TealAppDriver} object invisibly. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-remove_filter_var}{}}} -\subsection{\code{TealAppDriver$remove_filter_var()}}{ - Remove an active filter variable of a dataset from the active filter variables panel. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$remove_filter_var(dataset_name = NULL, var_name = NULL)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{dataset_name}}{(character) The name of the dataset to remove the filter variable from. +\subsection{Method \code{remove_filter_var()}}{ +Remove an active filter variable of a dataset from the active filter variables panel. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$remove_filter_var(dataset_name = NULL, var_name = NULL)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{dataset_name}}{(character) The name of the dataset to remove the filter variable from. If \code{NULL}, all the filter variables will be removed.} - \item{\code{var_name}}{(character) The name of the variable to remove from the filter panel. + +\item{\code{var_name}}{(character) The name of the variable to remove from the filter panel. If \code{NULL}, all the filter variables of the dataset will be removed.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The \code{TealAppDriver} object invisibly. - } } - +\if{html}{\out{
}} +} +\subsection{Returns}{ +The \code{TealAppDriver} object invisibly. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_active_filter_selection}{}}} -\subsection{\code{TealAppDriver$set_active_filter_selection()}}{ - Set the active filter values for a variable of a dataset in the active filter variable panel. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$set_active_filter_selection(dataset_name, var_name, input, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{dataset_name}}{(character) The name of the dataset to set the filter value for.} - \item{\code{var_name}}{(character) The name of the variable to set the filter value for.} - \item{\code{input}}{The value to set the filter to.} - \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The \code{TealAppDriver} object invisibly. - } +\subsection{Method \code{set_active_filter_selection()}}{ +Set the active filter values for a variable of a dataset in the active filter variable panel. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$set_active_filter_selection(dataset_name, var_name, input, ...)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{dataset_name}}{(character) The name of the dataset to set the filter value for.} + +\item{\code{var_name}}{(character) The name of the variable to set the filter value for.} + +\item{\code{input}}{The value to set the filter to.} + +\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The \code{TealAppDriver} object invisibly. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_attr}{}}} -\subsection{\code{TealAppDriver$get_attr()}}{ - Extract \code{html} attribute (found by a \code{selector}). - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_attr(selector, attribute)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{selector}}{(\code{character(1)}) specifying the selector to be used to get the content of a specific node.} - \item{\code{attribute}}{(\code{character(1)}) name of an attribute to retrieve from a node specified by \code{selector}.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - The \code{character} vector. - } +\subsection{Method \code{get_attr()}}{ +Extract \code{html} attribute (found by a \code{selector}). +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_attr(selector, attribute)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{selector}}{(\code{character(1)}) specifying the selector to be used to get the content of a specific node.} + +\item{\code{attribute}}{(\code{character(1)}) name of an attribute to retrieve from a node specified by \code{selector}.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +The \code{character} vector. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_html_rvest}{}}} -\subsection{\code{TealAppDriver$get_html_rvest()}}{ - Wrapper around \code{get_html} that passes the output directly to \code{rvest::read_html}. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$get_html_rvest(selector)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{selector}}{\code{(character(1))} passed to \code{get_html}.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - An XML document. -Wrapper around \code{get_url()} method that opens the app in the browser. - } +\subsection{Method \code{get_html_rvest()}}{ +Wrapper around \code{get_html} that passes the output directly to \code{rvest::read_html}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$get_html_rvest(selector)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{selector}}{\code{(character(1))} passed to \code{get_html}.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +An XML document. +Wrapper around \code{get_url()} method that opens the app in the browser. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-open_url}{}}} -\subsection{\code{TealAppDriver$open_url()}}{ - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$open_url()} - \if{html}{\out{
}} - } - \subsection{Returns}{ - Nothing. Opens the underlying teal app in the browser. - } +\subsection{Method \code{open_url()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$open_url()}\if{html}{\out{
}} } +\subsection{Returns}{ +Nothing. Opens the underlying teal app in the browser. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-wait_for_active_module_value}{}}} -\subsection{\code{TealAppDriver$wait_for_active_module_value()}}{ - Waits until a specified input, output, or export value. +\subsection{Method \code{wait_for_active_module_value()}}{ +Waits until a specified input, output, or export value. This function serves as a wrapper around the \code{wait_for_value} method, providing a more flexible interface for waiting on different types of values within the active module namespace. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealAppDriver$wait_for_active_module_value( +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealAppDriver$wait_for_active_module_value( input = rlang::missing_arg(), output = rlang::missing_arg(), export = rlang::missing_arg(), ... -)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{input, output, export}}{A name of an input, output, or export value. +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{input, output, export}}{A name of an input, output, or export value. Only one of these parameters may be used.} - \item{\code{...}}{Must be empty. Allows for parameter expansion. + +\item{\code{...}}{Must be empty. Allows for parameter expansion. Parameter with additional value to passed in \code{wait_for_value}.} - } - \if{html}{\out{
}} - } } - +\if{html}{\out{
}} +} +} } diff --git a/man/TealReportCard.Rd b/man/TealReportCard.Rd index c7aee7a256..8067c8002b 100644 --- a/man/TealReportCard.Rd +++ b/man/TealReportCard.Rd @@ -13,7 +13,7 @@ meta data. \examples{ ## ------------------------------------------------ -## Method `TealReportCard$append_src()` +## Method `TealReportCard$append_src` ## ------------------------------------------------ card <- TealReportCard$new()$append_src( @@ -22,7 +22,7 @@ card <- TealReportCard$new()$append_src( card$get_content()[[1]] ## ------------------------------------------------ -## Method `TealReportCard$append_encodings()` +## Method `TealReportCard$append_encodings` ## ------------------------------------------------ card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) @@ -34,137 +34,137 @@ card$get_content()[[1]] } \section{Methods}{ \subsection{Public methods}{ - \itemize{ - \item \href{#method-TealReportCard-append_src}{\code{TealReportCard$append_src()}} - \item \href{#method-TealReportCard-append_fs}{\code{TealReportCard$append_fs()}} - \item \href{#method-TealReportCard-append_encodings}{\code{TealReportCard$append_encodings()}} - \item \href{#method-TealReportCard-clone}{\code{TealReportCard$clone()}} - } -} -\if{html}{\out{
Inherited methods +\itemize{ +\item \href{#method-TealReportCard-append_src}{\code{TealReportCard$append_src()}} +\item \href{#method-TealReportCard-append_fs}{\code{TealReportCard$append_fs()}} +\item \href{#method-TealReportCard-append_encodings}{\code{TealReportCard$append_encodings()}} +\item \href{#method-TealReportCard-clone}{\code{TealReportCard$clone()}} +} +} +\if{html}{\out{ +
Inherited methods -
}} +
+}} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_src}{}}} -\subsection{\code{TealReportCard$append_src()}}{ - Appends the source code to the \code{content} meta data of this \code{TealReportCard}. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealReportCard$append_src(src, ...)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{src}}{(\code{character(1)}) code as text.} - \item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. +\subsection{Method \code{append_src()}}{ +Appends the source code to the \code{content} meta data of this \code{TealReportCard}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealReportCard$append_src(src, ...)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{src}}{(\code{character(1)}) code as text.} + +\item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. But \code{eval} parameter is always set to \code{FALSE}.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - Object of class \code{TealReportCard}, invisibly. - } - \subsection{Examples}{ - \if{html}{\out{
}} - \preformatted{card <- TealReportCard$new()$append_src( +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +Object of class \code{TealReportCard}, invisibly. +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{card <- TealReportCard$new()$append_src( "plot(iris)" ) card$get_content()[[1]] } - \if{html}{\out{
}} - } +\if{html}{\out{
}} + } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_fs}{}}} -\subsection{\code{TealReportCard$append_fs()}}{ - Appends the filter state list to the \code{content} and \code{metadata} of this \code{TealReportCard}. +\subsection{Method \code{append_fs()}}{ +Appends the filter state list to the \code{content} and \code{metadata} of this \code{TealReportCard}. If the filter state list has an attribute named \code{formatted}, it appends it to the card otherwise it uses the default \code{yaml::as.yaml} to format the list. If the filter state list is empty, nothing is appended to the \code{content}. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealReportCard$append_fs(fs)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{fs}}{(\code{teal_slices}) object returned from \code{\link[=teal_slices]{teal_slices()}} function.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - \code{self}, invisibly. - } +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealReportCard$append_fs(fs)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{fs}}{(\code{teal_slices}) object returned from \code{\link[=teal_slices]{teal_slices()}} function.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +\code{self}, invisibly. +} +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_encodings}{}}} -\subsection{\code{TealReportCard$append_encodings()}}{ - Appends the encodings list to the \code{content} and \code{metadata} of this \code{TealReportCard}. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealReportCard$append_encodings(encodings)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} - } - \if{html}{\out{
}} - } - \subsection{Returns}{ - \code{self}, invisibly. - } - \subsection{Examples}{ - \if{html}{\out{
}} - \preformatted{card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) +\subsection{Method \code{append_encodings()}}{ +Appends the encodings list to the \code{content} and \code{metadata} of this \code{TealReportCard}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealReportCard$append_encodings(encodings)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +\code{self}, invisibly. +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) card$get_content()[[1]] + } - \if{html}{\out{
}} - } +\if{html}{\out{
}} + } +} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-clone}{}}} -\subsection{\code{TealReportCard$clone()}}{ - The objects of this class are cloneable with this method. - \subsection{Usage}{ - \if{html}{\out{
}} - \preformatted{TealReportCard$clone(deep = FALSE)} - \if{html}{\out{
}} - } - \subsection{Arguments}{ - \if{html}{\out{
}} - \describe{ - \item{\code{deep}}{Whether to make a deep clone.} - } - \if{html}{\out{
}} - } +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TealReportCard$clone(deep = FALSE)}\if{html}{\out{
}} } +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} } diff --git a/man/dot-teal_favicon.Rd b/man/dot-teal_favicon.Rd index 5c55019bfd..9f20d66d8c 100644 --- a/man/dot-teal_favicon.Rd +++ b/man/dot-teal_favicon.Rd @@ -1,8 +1,12 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R +\docType{data} \name{.teal_favicon} \alias{.teal_favicon} \title{The default favicon for the teal app.} +\format{ +An object of class \code{character} of length 1. +} \usage{ .teal_favicon } diff --git a/man/reporter_previewer_module.Rd b/man/reporter_previewer_module.Rd index 202a7838f7..6f0ff86354 100644 --- a/man/reporter_previewer_module.Rd +++ b/man/reporter_previewer_module.Rd @@ -24,8 +24,8 @@ It is now deprecated in favor of the options: \itemize{ \item \code{teal.reporter.nav_buttons = c("preview", "download", "load", "reset")} to control which buttons will be displayed in the drop-down. -\item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} -\item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} -\item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} } } diff --git a/man/teal-package.Rd b/man/teal-package.Rd index 27e798d529..49f09ba63c 100644 --- a/man/teal-package.Rd +++ b/man/teal-package.Rd @@ -27,7 +27,6 @@ Useful links: Authors: \itemize{ - \item Dawid Kaledkowski \email{dawid.kaledkowski@roche.com} (\href{https://orcid.org/0000-0001-9533-457X}{ORCID}) \item Pawel Rucki \email{pawel.rucki@roche.com} \item Aleksander Chlebowski \email{aleksander.chlebowski@contractors.roche.com} (\href{https://orcid.org/0000-0001-5018-6294}{ORCID}) \item Andre Verissimo \email{andre.verissimo@roche.com} (\href{https://orcid.org/0000-0002-2212-339X}{ORCID}) diff --git a/man/validate_input.Rd b/man/validate_input.Rd index bb7b5e9dde..12f8597354 100644 --- a/man/validate_input.Rd +++ b/man/validate_input.Rd @@ -2,14 +2,25 @@ % Please edit documentation in R/validate_inputs.R \name{validate_input} \alias{validate_input} +\alias{need_input} +\alias{use_validate_input_js} \title{Validate input} \usage{ validate_input( inputId, condition = function(x) TRUE, - message = "", + message = "Check the input", session = shiny::getDefaultReactiveDomain() ) + +need_input( + inputId, + condition = function(x) TRUE, + message = "Check the input", + session = shiny::getDefaultReactiveDomain() +) + +use_validate_input_js() } \arguments{ \item{inputId}{(\code{character}) Character of input ID(s) to validate} @@ -17,7 +28,7 @@ validate_input( \item{condition}{(\code{logical(1)}, \verb{function(x)}) Logical value or function returning logical value. Condition should determine expected state, \code{FALSE} throws.} -\item{message}{(\code{character(1)}) Character string of validation message to display} +\item{message}{(\code{character(1)}) Character string of validation message to display.} \item{session}{Shiny session object} } @@ -25,6 +36,150 @@ Condition should determine expected state, \code{FALSE} throws.} \code{NULL} or \code{shiny.silent.error} when condition is not met } \description{ -Validate input +Validate a Shiny input and send validation messages to the client. +The message appears both in the input widget and in the output element +that calls \code{validate_input}. +} +\details{ +\itemize{ +\item \verb{validate_input():} + +Validate a Shiny input and send validation messages to the client. +The message appears both in the input widget and in the output element +that calls \code{validate_input}. +} + +\itemize{ +\item \code{need_input()}: Validate a Shiny input and returns a message to be used in a \code{\link[shiny:validate]{shiny::validate()}} call. +The message is sent to the client and appears both in the input widget. +To observe the message in the output element, \code{need_input()} should be called inside a \code{shiny::validate()} call, e.g. via \code{validate(validate_input(...))}. +} + +\itemize{ +\item \code{use_validate_input_js()}: + +Include JavaScript for client-side input validation. +} +} +\examples{ +# Only run examples in interactive R sessions +options(device.ask.default = FALSE) + +ui <- fluidPage( + checkboxGroupInput("in1", "Check some letters", choices = head(LETTERS)), + selectizeInput("in2", "Select a state", choices = c("", state.name)), + use_validate_input_js(), + plotOutput("plot") +) + +server <- function(input, output) { + output$plot <- renderPlot({ + validate_input("in1", condition = function(x) length(x) > 0, "Check at least one letter!") + validate_input("in2", condition = function(x) x != "", "Please choose a state.") + plot(1:10, main = paste(c(input$in1, input$in2), collapse = ", ")) + }) +} + +if (interactive()) { + shinyApp(ui, server) +} + +my_module <- module( + label = "My Module", + datanames = NULL, + ui = function(id) { + ns <- NS(id) + tagList( + checkboxGroupInput(ns("letters"), "Select letters:", choices = head(LETTERS), inline = TRUE), + checkboxGroupInput(ns("letters2"), "Select letters:", choices = head(LETTERS), inline = TRUE), + tags$h3("Sample plot"), + plotOutput(ns("plot")) + ) + }, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + output$plot <- renderPlot({ + validate_input( + "letters", + condition = function(x) length(x) > 0, + message = "Select at least one letter." + ) + validate_input( + c("letters", "letters2"), + condition = function(x, y) all(!x \%in\% y), + message = "Letters in the first group should not be in the second group." + ) + tab <- rbind( + Group1 = as.integer(head(LETTERS) \%in\% input$letters), + Group2 = as.integer(head(LETTERS) \%in\% input$letters2) + ) + colnames(tab) <- head(LETTERS) + barplot( + tab, + beside = TRUE, legend.text = TRUE, main = "Selected letters per group", + col = c("steelblue", "tomato") + ) + }) + }) + } +) + +app <- init( + data = within(teal_data(), iris <- iris), + modules = my_module +) + +if (interactive()) { + shinyApp(app$ui, app$server) +} +my_module <- module( + label = "My Module", + datanames = NULL, + ui = function(id) { + ns <- NS(id) + tagList( + checkboxGroupInput(ns("letters1"), "Select letters:", choices = head(LETTERS), inline = TRUE), + checkboxGroupInput(ns("letters2"), "Select letters:", choices = head(LETTERS), inline = TRUE), + tags$h3("Sample plot"), + plotOutput(ns("plot")) + ) + }, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + output$plot <- renderPlot({ + validate( + need_input( + "letters1", + condition = function(x) length(x) > 0, + message = "Select at least one letter." + ), + need_input( + c("letters1", "letters2"), + condition = function(x, y) all(!x \%in\% y), + message = "Letters in the first group should not be in the second group." + ) + ) + tab <- rbind( + Group1 = as.integer(head(LETTERS) \%in\% input$letters1), + Group2 = as.integer(head(LETTERS) \%in\% input$letters2) + ) + colnames(tab) <- head(LETTERS) + barplot( + tab, + beside = TRUE, legend.text = TRUE, main = "Selected letters per group", + col = c("steelblue", "tomato") + ) + }) + }) + } +) + +app <- init( + data = within(teal_data(), iris <- iris), + modules = my_module +) + +if (interactive()) { + shinyApp(app$ui, app$server) +} } -\keyword{internal} diff --git a/tests/testthat/test-shinytest2-modules.R b/tests/testthat/test-shinytest2-modules.R index 497976c2d2..12f184e3d5 100644 --- a/tests/testthat/test-shinytest2-modules.R +++ b/tests/testthat/test-shinytest2-modules.R @@ -121,13 +121,14 @@ testthat::test_that("e2e: active tab is reflected in URL query string after navi example_module(label = "mod1"), example_module(label = "mod2") ) - ) + ), + teal_options = list(teal.enable_deep_linking = TRUE) ) on.exit(app$stop()) app$navigate_teal_tab("mod2") - testthat::expect_match(app$get_url(), "active_module=mod2") + testthat::expect_match(app$get_js("window.location.href"), "active_module=mod2") }) testthat::test_that("e2e: URL query string active_module switches to the specified tab", { @@ -139,7 +140,8 @@ testthat::test_that("e2e: URL query string active_module switches to the specifi example_module(label = "mod1"), example_module(label = "mod2") ) - ) + ), + teal_options = list(teal.enable_deep_linking = TRUE) ) on.exit(app$stop()) diff --git a/tests/testthat/test-shinytest2-teal_data_module.R b/tests/testthat/test-shinytest2-teal_data_module.R index c065d436a7..67b4fb4a48 100644 --- a/tests/testthat/test-shinytest2-teal_data_module.R +++ b/tests/testthat/test-shinytest2-teal_data_module.R @@ -98,7 +98,7 @@ testthat::test_that("e2e: teal_data_module doesn't auto-close when `once=FALSE` app$stop() }) -testthat::test_that("e2e: teal_data_module doesn't auto-close when `once=FALSE` and data is ready (no submit)", { +testthat::test_that("e2e: teal_data_module auto-closes modal when `once=FALSE` and data is ready (no submit)", { skip_if_too_deep(5) app <- TealAppDriver$new( init( @@ -106,7 +106,20 @@ testthat::test_that("e2e: teal_data_module doesn't auto-close when `once=FALSE` modules = example_module(label = "Example Module") ) ) + testthat::expect_null(app$get_html(".teal-data-module-popup")) + app$stop() +}) + +testthat::test_that("e2e: teal_data_module modal stays visible on startup when `once=FALSE` and submit button is present", { + skip_if_too_deep(5) + app <- TealAppDriver$new( + init( + data = example_teal_data_module(needs_submit = TRUE, once = FALSE), + modules = example_module(label = "Example Module") + ) + ) app$expect_visible(".teal-data-module-popup") + app$expect_visible("#teal-teal_data_module-submit") app$stop() }) diff --git a/tests/testthat/test-shinytest2-validate_input.R b/tests/testthat/test-shinytest2-validate_input.R index b6e49e85f1..2436964b20 100644 --- a/tests/testthat/test-shinytest2-validate_input.R +++ b/tests/testthat/test-shinytest2-validate_input.R @@ -10,7 +10,7 @@ testthat::test_that("e2e: validate_input displays validation message when input server = function(id, data) { moduleServer(id, function(input, output, session) { output$plot <- renderPlot({ - teal:::validate_input( + validate_input( inputId = "number", condition = function(x) !is.null(x) && as.numeric(x) > 5, message = "Please select a number greater than 5", @@ -60,7 +60,7 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu server = function(id, data) { moduleServer(id, function(input, output, session) { output$result <- renderText({ - teal:::validate_input( + validate_input( inputId = c("input1", "input2"), condition = function(x, y) identical(x, y), message = "x and y must be identical", @@ -89,7 +89,6 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu ) withr::defer(app_driver$stop()) - # Initially input2 is invalid (value 5 < 10) error_text <- app_driver$get_text(".shiny-output-error-validation") testthat::expect_match(error_text, "x and y must be identical") testthat::expect_match( @@ -112,7 +111,7 @@ testthat::test_that("e2e: validate_input validates many inputs (and linked outpu ) }) -testthat::describe("e2e: validate_input validates", { +testthat::describe("e2e: validate_input validates generic fields", { skip_if_too_deep(5) all_inputs_mod <- module( label = "all inputs", @@ -131,42 +130,21 @@ testthat::describe("e2e: validate_input validates", { server = function(id, data) { moduleServer(id, function(input, output, session) { reactive({ - validation <- list( - try( - teal:::validate_input("select", function(x) identical(x, "B"), "select must be B"), - silent = TRUE + validate( + need_input("select", function(x) identical(x, "B"), "select must be B"), + need_input("selectize", function(x) identical(x, "Y"), "selectize must be Y"), + need_input("radio", function(x) identical(x, "Option 2"), "radio must be Option 2"), + need_input( + "checkbox_group", + function(x) identical(x, c("Check 2")), + "checkbox_group must be Check 2" ), - try( - teal:::validate_input("selectize", function(x) identical(x, "Y"), "selectize must be Y", ), - silent = TRUE - ), - try( - teal:::validate_input("radio", function(x) identical(x, "Option 2"), "radio must be Option 2"), - silent = TRUE - ), - try( - teal:::validate_input( - "checkbox_group", - function(x) identical(x, c("Check 2")), - "checkbox_group must be Check 2" - ), - silent = TRUE - ), - try( - teal:::validate_input("checkbox", function(x) identical(x, TRUE), "checkbox must be TRUE"), - silent = TRUE - ), - try( - teal:::validate_input("date", function(x) identical(x, as.Date("2024-01-01")), "date must be 2024-01-01"), - silent = TRUE - ), - try( - teal:::validate_input( - "date_range", - function(x) identical(x, c(as.Date("2024-01-01"), as.Date("2024-01-31"))), - "date_range must be 2024-01-01 to 2024-01-31" - ), - silent = TRUE + need_input("checkbox", function(x) identical(x, TRUE), "checkbox must be TRUE"), + need_input("date", function(x) identical(x, as.Date("2024-01-01")), "date must be 2024-01-01"), + need_input( + "date_range", + function(x) identical(x, c(as.Date("2024-01-01"), as.Date("2024-01-31"))), + "date_range must be 2024-01-01 to 2024-01-31" ) ) data() @@ -182,70 +160,175 @@ testthat::describe("e2e: validate_input validates", { message <- "select must be B" testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("select"), "B") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) + testthat::expect_no_match(app_driver$get_text(".shiny-output-error") %||% character(0L), message, fixed = TRUE) }) it("selectizeInput", { message <- "selectize must be Y" testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("selectize"), "Y") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) + testthat::expect_no_match(app_driver$get_text(".shiny-output-error") %||% character(0L), message, fixed = TRUE) }) it("radioButtons", { message <- "radio must be Option 2" testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("radio"), "Option 2") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) + testthat::expect_no_match(app_driver$get_text(".shiny-output-error") %||% character(0L), message, fixed = TRUE) }) it("checkboxGroupInput", { message <- "checkbox_group must be Check 2" testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("checkbox_group"), "Check 2") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) + testthat::expect_no_match(app_driver$get_text(".shiny-output-error") %||% character(0L), message, fixed = TRUE) }) it("checkboxInput", { message <- "checkbox must be TRUE" testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("checkbox"), TRUE) - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) + testthat::expect_no_match(app_driver$get_text(".shiny-output-error") %||% character(0L), message, fixed = TRUE) }) it("dateInput", { message <- "date must be 2024-01-01" testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("date"), "2024-01-01") - testthat::expect_failure( - testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) - ) + testthat::expect_no_match(app_driver$get_text(".shiny-output-error") %||% character(0L), message, fixed = TRUE) }) it("dateRangeInput", { message <- "date_range must be 2024-01-01 to 2024-01-31" testthat::expect_match(app_driver$get_text(".shiny-output-error"), message, fixed = TRUE, all = FALSE) app_driver$set_input(app_driver$namespaces()$module("date_range"), c("2024-01-01", "2024-01-31")) - errors <- app_driver$get_text(".shiny-output-error") - if (is.null(errors)) { # Set to empty character vector to avoid testthat::expect_match error - errors <- character(0L) + testthat::expect_no_match(app_driver$get_text(".shiny-output-error") %||% character(0L), message, fixed = TRUE) + }) +}) + +testthat::test_that("validate_input shinytest2 - sequence of errors with parallel validation", { + my_module <- module( + label = "My Module", + datanames = NULL, + ui = function(id) { + ns <- NS(id) + tagList( + checkboxGroupInput(ns("letters1"), "Select letters:", choices = head(LETTERS), inline = TRUE), + checkboxGroupInput(ns("letters2"), "Select letters:", choices = head(LETTERS), inline = TRUE), + tags$h3("Sample plot"), + plotOutput(ns("plot")) + ) + }, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + output$plot <- renderPlot({ + validate( + need_input( + "letters1", + condition = length(input$letters1) > 0, + message = "Select at least one letter." + ), + need_input( + c("letters1", "letters2"), + condition = function(x, y) all(!x %in% y), + message = "Letters in the first group should not be in the second group." + ) + ) + tab <- rbind( + Group1 = as.integer(head(LETTERS) %in% input$letters1), + Group2 = as.integer(head(LETTERS) %in% input$letters2) + ) + colnames(tab) <- head(LETTERS) + barplot( + tab, + beside = TRUE, legend.text = TRUE, main = "Selected letters per group", + col = c("steelblue", "tomato") + ) + }) + }) } - testthat::expect_failure( - testthat::expect_match(errors, message, fixed = TRUE, all = FALSE) + ) + + app_driver <- TealAppDriver$new( + app = init( + data = within(teal_data(), iris <- iris), + modules = my_module ) - }) + ) + withr::defer(app_driver$stop()) - it("no input errors at the end", { - testthat::expect_null(app_driver$get_text(".shiny-output-error")) - }) + # Shows 1 error messages in initialization + testthat::expect_equal( + app_driver$get_text( + "#teal-teal_modules-nav-my_module-module-letters1 ~ .shiny-output-error.shiny-input-validation-error" + ), + "Select at least one letter." + ) + testthat::expect_length(app_driver$get_text(".shiny-output-error.shiny-input-validation-error"), 1) + # Shows error in output + testthat::expect_equal( + app_driver$get_text("#teal-teal_modules-nav-my_module-module-plot"), + "Select at least one letter." + ) + # after clicking on first letter no errors appear + app_driver$set_inputs("teal-teal_modules-nav-my_module-module-letters1" = "A") + testthat::expect_null(app_driver$get_text(".shiny-output-error.shiny-input-validation-error")) + + # after clicking on the same letter in the second group validation error appears + app_driver$set_inputs("teal-teal_modules-nav-my_module-module-letters2" = "A") + testthat::expect_equal( + app_driver$get_text(".shiny-output-error.shiny-input-validation-error"), + rep("Letters in the first group should not be in the second group.", 2) + ) +}) + +testthat::test_that("validate_input shinytest2 - stale messages are not shown", { + my_module <- module( + label = "My Module", + datanames = NULL, + ui = function(id) { + ns <- NS(id) + tagList( + textInput(ns("letters1"), "Select letters:", value = "A"), + textInput(ns("letters2"), "Select letters:", value = ""), + uiOutput(ns("validation_message")) + ) + }, + server = function(id, data) { + moduleServer(id, function(input, output, session) { + output$validation_message <- renderUI({ + validate( + need_input( + "letters1", + condition = nchar(input$letters1) > 0, + message = "Select at least one letter for first input." + ) + ) + validate( + need_input( + c("letters2"), + condition = nchar(input$letters2) > 0, + message = "Select at least one letter for second input." + ) + ) + tags$div("All inputs are valid.") + }) + }) + } + ) + + app_driver <- TealAppDriver$new( + app = init( + data = within(teal_data(), iris <- iris), + modules = my_module + ) + ) + withr::defer(app_driver$stop()) + + app_driver$set_active_module_input("letters1", character(0)) + app_driver$set_active_module_input("letters2", "B") + testthat::expect_no_match( + app_driver$get_text(".shiny-input-validation-error"), + "Select at least one letter for second input." + ) }) diff --git a/tests/testthat/test-validate_input.R b/tests/testthat/test-validate_input.R index eecd4b9b6e..fbc3738611 100644 --- a/tests/testthat/test-validate_input.R +++ b/tests/testthat/test-validate_input.R @@ -186,3 +186,69 @@ testthat::test_that("validate_input with multiple inputIds doesn't throw shiny.s args = list(id = "test") ) }) + +testthat::describe("validate with multiple need_input", { + it("1 validation", { + test_module <- function(id) { + moduleServer(id, function(input, output, session) { + reactive({ + validate( + need_input( + inputId = "test_input", + condition = function(x) is.null(x), + message = "input must be null", + session = session + ) + ) + }) + }) + } + + shiny::testServer( + app = test_module, + args = list(id = "test"), + expr = { + # Set up input value + testthat::expect_no_error(session$returned()) + session$setInputs(test_input = "invalid_value") + testthat::expect_error(session$returned(), "input must be null") + } + ) + }) + + it("2 validations", { + test_module <- function(id) { + moduleServer(id, function(input, output, session) { + reactive({ + validate( + need_input( + inputId = "test_input1", + condition = function(x) is.null(x), + message = "input1 must be null", + session = session + ), + need_input( + inputId = "test_input2", + condition = function(x) is.null(x), + message = "input2 must be null", + session = session + ) + ) + }) + }) + } + + shiny::testServer( + app = test_module, + args = list(id = "test"), + expr = { + # Set up input value + testthat::expect_no_error(session$returned()) + session$setInputs(test_input1 = "invalid_value", test_input2 = "invalid_value") + testthat::expect_error(session$returned(), "input1 must be null\ninput2 must be null") + session$setInputs(test_input1 = NULL, test_input2 = "invalid_value") + testthat::expect_error(session$returned(), "^input2 must be null$") + } + ) + }) +}) From d54a0512040f7ff4296fffd1a945982dd9a90990 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 21:51:37 +0000 Subject: [PATCH 30/33] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/TealAppDriver.Rd | 950 ++++++++++++++++--------------- man/TealReportCard.Rd | 206 +++---- man/dot-teal_favicon.Rd | 4 - man/reporter_previewer_module.Rd | 6 +- man/teal-package.Rd | 1 + 5 files changed, 594 insertions(+), 573 deletions(-) diff --git a/man/TealAppDriver.Rd b/man/TealAppDriver.Rd index 7435125985..ae28654951 100644 --- a/man/TealAppDriver.Rd +++ b/man/TealAppDriver.Rd @@ -4,11 +4,6 @@ \alias{TealAppDriver} \title{Drive a \code{teal} application} \description{ -Drive a \code{teal} application - -Drive a \code{teal} application -} -\details{ Extension of the \code{shinytest2::AppDriver} class with methods for driving a teal application for performing interactions for \code{shinytest2} tests. } @@ -18,652 +13,681 @@ driving a teal application for performing interactions for \code{shinytest2} tes } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-TealAppDriver-new}{\code{TealAppDriver$new()}} -\item \href{#method-TealAppDriver-stop}{\code{TealAppDriver$stop()}} -\item \href{#method-TealAppDriver-click}{\code{TealAppDriver$click()}} -\item \href{#method-TealAppDriver-expect_no_shiny_error}{\code{TealAppDriver$expect_no_shiny_error()}} -\item \href{#method-TealAppDriver-expect_no_validation_error}{\code{TealAppDriver$expect_no_validation_error()}} -\item \href{#method-TealAppDriver-expect_validation_error}{\code{TealAppDriver$expect_validation_error()}} -\item \href{#method-TealAppDriver-set_input}{\code{TealAppDriver$set_input()}} -\item \href{#method-TealAppDriver-navigate_teal_tab}{\code{TealAppDriver$navigate_teal_tab()}} -\item \href{#method-TealAppDriver-namespaces}{\code{TealAppDriver$namespaces()}} -\item \href{#method-TealAppDriver-get_active_module_input}{\code{TealAppDriver$get_active_module_input()}} -\item \href{#method-TealAppDriver-get_active_module_output}{\code{TealAppDriver$get_active_module_output()}} -\item \href{#method-TealAppDriver-get_active_module_table_output}{\code{TealAppDriver$get_active_module_table_output()}} -\item \href{#method-TealAppDriver-get_active_module_plot_output}{\code{TealAppDriver$get_active_module_plot_output()}} -\item \href{#method-TealAppDriver-set_active_module_input}{\code{TealAppDriver$set_active_module_input()}} -\item \href{#method-TealAppDriver-get_active_filter_vars}{\code{TealAppDriver$get_active_filter_vars()}} -\item \href{#method-TealAppDriver-get_active_data_summary_table}{\code{TealAppDriver$get_active_data_summary_table()}} -\item \href{#method-TealAppDriver-is_visible}{\code{TealAppDriver$is_visible()}} -\item \href{#method-TealAppDriver-expect_visible}{\code{TealAppDriver$expect_visible()}} -\item \href{#method-TealAppDriver-expect_hidden}{\code{TealAppDriver$expect_hidden()}} -\item \href{#method-TealAppDriver-get_active_data_filters}{\code{TealAppDriver$get_active_data_filters()}} -\item \href{#method-TealAppDriver-add_filter_var}{\code{TealAppDriver$add_filter_var()}} -\item \href{#method-TealAppDriver-remove_filter_var}{\code{TealAppDriver$remove_filter_var()}} -\item \href{#method-TealAppDriver-set_active_filter_selection}{\code{TealAppDriver$set_active_filter_selection()}} -\item \href{#method-TealAppDriver-get_attr}{\code{TealAppDriver$get_attr()}} -\item \href{#method-TealAppDriver-get_html_rvest}{\code{TealAppDriver$get_html_rvest()}} -\item \href{#method-TealAppDriver-open_url}{\code{TealAppDriver$open_url()}} -\item \href{#method-TealAppDriver-wait_for_active_module_value}{\code{TealAppDriver$wait_for_active_module_value()}} -} -} -\if{html}{\out{ -
Inherited methods + \itemize{ + \item \href{#method-TealAppDriver-initialize}{\code{TealAppDriver$new()}} + \item \href{#method-TealAppDriver-stop}{\code{TealAppDriver$stop()}} + \item \href{#method-TealAppDriver-click}{\code{TealAppDriver$click()}} + \item \href{#method-TealAppDriver-expect_no_shiny_error}{\code{TealAppDriver$expect_no_shiny_error()}} + \item \href{#method-TealAppDriver-expect_no_validation_error}{\code{TealAppDriver$expect_no_validation_error()}} + \item \href{#method-TealAppDriver-expect_validation_error}{\code{TealAppDriver$expect_validation_error()}} + \item \href{#method-TealAppDriver-set_input}{\code{TealAppDriver$set_input()}} + \item \href{#method-TealAppDriver-navigate_teal_tab}{\code{TealAppDriver$navigate_teal_tab()}} + \item \href{#method-TealAppDriver-namespaces}{\code{TealAppDriver$namespaces()}} + \item \href{#method-TealAppDriver-get_active_module_input}{\code{TealAppDriver$get_active_module_input()}} + \item \href{#method-TealAppDriver-get_active_module_output}{\code{TealAppDriver$get_active_module_output()}} + \item \href{#method-TealAppDriver-get_active_module_table_output}{\code{TealAppDriver$get_active_module_table_output()}} + \item \href{#method-TealAppDriver-get_active_module_plot_output}{\code{TealAppDriver$get_active_module_plot_output()}} + \item \href{#method-TealAppDriver-set_active_module_input}{\code{TealAppDriver$set_active_module_input()}} + \item \href{#method-TealAppDriver-get_active_filter_vars}{\code{TealAppDriver$get_active_filter_vars()}} + \item \href{#method-TealAppDriver-get_active_data_summary_table}{\code{TealAppDriver$get_active_data_summary_table()}} + \item \href{#method-TealAppDriver-is_visible}{\code{TealAppDriver$is_visible()}} + \item \href{#method-TealAppDriver-expect_visible}{\code{TealAppDriver$expect_visible()}} + \item \href{#method-TealAppDriver-expect_hidden}{\code{TealAppDriver$expect_hidden()}} + \item \href{#method-TealAppDriver-get_active_data_filters}{\code{TealAppDriver$get_active_data_filters()}} + \item \href{#method-TealAppDriver-add_filter_var}{\code{TealAppDriver$add_filter_var()}} + \item \href{#method-TealAppDriver-remove_filter_var}{\code{TealAppDriver$remove_filter_var()}} + \item \href{#method-TealAppDriver-set_active_filter_selection}{\code{TealAppDriver$set_active_filter_selection()}} + \item \href{#method-TealAppDriver-get_attr}{\code{TealAppDriver$get_attr()}} + \item \href{#method-TealAppDriver-get_html_rvest}{\code{TealAppDriver$get_html_rvest()}} + \item \href{#method-TealAppDriver-open_url}{\code{TealAppDriver$open_url()}} + \item \href{#method-TealAppDriver-wait_for_active_module_value}{\code{TealAppDriver$wait_for_active_module_value()}} + } +} +\if{html}{\out{
Inherited methods -
-}} +
}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-TealAppDriver-new}{}}} -\subsection{Method \code{new()}}{ -Initialize a \code{TealAppDriver} object for testing a \code{teal} application. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$new( +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TealAppDriver-initialize}{}}} +\subsection{\code{TealAppDriver$new()}}{ + Initialize a \code{TealAppDriver} object for testing a \code{teal} application. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$new( app, options = list(), teal_options = list(), timeout = rlang::missing_arg(), load_timeout = rlang::missing_arg(), ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{app}}{(\code{teal_app})} - -\item{\code{options}}{(\code{list}) passed to \code{shinyApp(options)}. See \code{\link[shiny:shinyApp]{shiny::shinyApp()}}.} - -\item{\code{teal_options}}{(\code{list}) named list of R options to set inside the spawned +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{app}}{(\code{teal_app})} + \item{\code{options}}{(\code{list}) passed to \code{shinyApp(options)}. See \code{\link[shiny:shinyApp]{shiny::shinyApp()}}.} + \item{\code{teal_options}}{(\code{list}) named list of R options to set inside the spawned Shiny process (e.g. \code{list(teal.enable_deep_linking = TRUE)}). Applied at session start so option-gated server logic sees them.} - -\item{\code{timeout}}{(\code{numeric}) Default number of milliseconds for any timeout or + \item{\code{timeout}}{(\code{numeric}) Default number of milliseconds for any timeout or timeout_ parameter in the \code{TealAppDriver} class. Defaults to 20s. See \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{new} method for more details on how to change it via options or environment variables.} - -\item{\code{load_timeout}}{(\code{numeric}) How long to wait for the app to load, in ms. + \item{\code{load_timeout}}{(\code{numeric}) How long to wait for the app to load, in ms. This includes the time to start R. Defaults to 100s. See \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{new} method for more details on how to change it via options or environment variables} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$new}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -Object of class \code{TealAppDriver} -} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$new}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Object of class \code{TealAppDriver} + } } + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-stop}{}}} -\subsection{Method \code{stop()}}{ -Extension of the parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{stop} method that prints the logs +\subsection{\code{TealAppDriver$stop()}}{ + Extension of the parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{stop} method that prints the logs if the \code{ACTIONS_STEP_DEBUG} environment variable is set to \code{true} (case of value is ignored). -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$stop(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$stop(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-click}{}}} -\subsection{Method \code{click()}}{ -Append parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click} method with a call to \code{waif_for_idle()} method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$click(...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$click()}}{ + Append parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click} method with a call to \code{waif_for_idle()} method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$click(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_no_shiny_error}{}}} -\subsection{Method \code{expect_no_shiny_error()}}{ -Check if the app has shiny errors. This checks for global shiny errors. +\subsection{\code{TealAppDriver$expect_no_shiny_error()}}{ + Check if the app has shiny errors. This checks for global shiny errors. Note that any shiny errors dependent on shiny server render will only be captured after the teal module tab is visited because shiny will not trigger server computations when the tab is invisible. So, navigate to the module tab you want to test before calling this function. Although, this catches errors hidden in the other module tabs if they are already rendered. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_no_shiny_error()}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_no_shiny_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_no_validation_error}{}}} -\subsection{Method \code{expect_no_validation_error()}}{ -Check if the app has no validation errors. This checks for global shiny validation errors. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_no_validation_error()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$expect_no_validation_error()}}{ + Check if the app has no validation errors. This checks for global shiny validation errors. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_no_validation_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_validation_error}{}}} -\subsection{Method \code{expect_validation_error()}}{ -Check if the app has validation errors. This checks for global shiny validation errors. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_validation_error()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$expect_validation_error()}}{ + Check if the app has validation errors. This checks for global shiny validation errors. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_validation_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_input}{}}} -\subsection{Method \code{set_input()}}{ -Set the input in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_input(input_id, value, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$set_input()}}{ + Set the input in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_input(input_id, value, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id with it's complete name space.} + \item{\code{value}}{The value to set the input to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id with it's complete name space.} - -\item{\code{value}}{The value to set the input to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-navigate_teal_tab}{}}} -\subsection{Method \code{navigate_teal_tab()}}{ -Navigate the teal tabs in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$navigate_teal_tab(tab)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{tab}}{(character) Labels of tabs to navigate to. +\subsection{\code{TealAppDriver$navigate_teal_tab()}}{ + Navigate the teal tabs in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$navigate_teal_tab(tab)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{tab}}{(character) Labels of tabs to navigate to. Note: Make sure to provide unique labels for the tabs.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-namespaces}{}}} -\subsection{Method \code{namespaces()}}{ -\code{NS} in different sections of \code{teal} app -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$namespaces(is_selector = FALSE)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$namespaces()}}{ + \code{NS} in different sections of \code{teal} app + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$namespaces(is_selector = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{is_selector}}{(\code{logical(1)}) whether \code{ns} function should prefix with \verb{#}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + list of \code{ns}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{is_selector}}{(\code{logical(1)}) whether \code{ns} function should prefix with \verb{#}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -list of \code{ns}. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_input}{}}} -\subsection{Method \code{get_active_module_input()}}{ -Get the input from the module in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_input()}}{ + Get the input from the module in the \code{teal} app. This function will only access inputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_input(input_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_input(input_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id to get the value from.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The value of the shiny input. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id to get the value from.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The value of the shiny input. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_output}{}}} -\subsection{Method \code{get_active_module_output()}}{ -Get the output from the module in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_output()}}{ + Get the output from the module in the \code{teal} app. This function will only access outputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_output(output_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_output(output_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{output_id}}{(character) The shiny output id to get the value from.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The value of the shiny output. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{output_id}}{(character) The shiny output id to get the value from.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The value of the shiny output. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_table_output}{}}} -\subsection{Method \code{get_active_module_table_output()}}{ -Get the output from the module's \code{teal.widgets::table_with_settings} or \code{DT::DTOutput} in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_table_output()}}{ + Get the output from the module's \code{teal.widgets::table_with_settings} or \code{DT::DTOutput} in the \code{teal} app. This function will only access outputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_table_output(table_id, which = 1)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{table_id}}{(\code{character(1)}) The id of the table in the active teal module's name space.} - -\item{\code{which}}{(integer) If there is more than one table, which should be extracted. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_table_output(table_id, which = 1)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{table_id}}{(\code{character(1)}) The id of the table in the active teal module's name space.} + \item{\code{which}}{(integer) If there is more than one table, which should be extracted. By default it will look for a table that is built using \code{teal.widgets::table_with_settings}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The data.frame with table contents. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The data.frame with table contents. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_plot_output}{}}} -\subsection{Method \code{get_active_module_plot_output()}}{ -Get the output from the module's \code{teal.widgets::plot_with_settings} in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_plot_output()}}{ + Get the output from the module's \code{teal.widgets::plot_with_settings} in the \code{teal} app. This function will only access plots from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_plot_output(plot_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_plot_output(plot_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{plot_id}}{(\code{character(1)}) The id of the plot in the active teal module's name space.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{src} attribute as \code{character(1)} vector. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{plot_id}}{(\code{character(1)}) The id of the plot in the active teal module's name space.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{src} attribute as \code{character(1)} vector. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_active_module_input}{}}} -\subsection{Method \code{set_active_module_input()}}{ -Set the input in the module in the \code{teal} app. +\subsection{\code{TealAppDriver$set_active_module_input()}}{ + Set the input in the module in the \code{teal} app. This function will only set inputs in the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_active_module_input(input_id, value, ...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_active_module_input(input_id, value, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id to get the value from.} + \item{\code{value}}{The value to set the input to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id to get the value from.} - -\item{\code{value}}{The value to set the input to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_filter_vars}{}}} -\subsection{Method \code{get_active_filter_vars()}}{ -Get the active datasets that can be accessed via the filter panel of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_filter_vars()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_active_filter_vars()}}{ + Get the active datasets that can be accessed via the filter panel of the current active teal module. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_filter_vars()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_data_summary_table}{}}} -\subsection{Method \code{get_active_data_summary_table()}}{ -Get the active data summary table -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_data_summary_table()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_active_data_summary_table()}}{ + Get the active data summary table + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_data_summary_table()} + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{data.frame} + } } -\subsection{Returns}{ -\code{data.frame} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-is_visible}{}}} -\subsection{Method \code{is_visible()}}{ -Test if \code{DOM} elements are visible on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$is_visible( +\subsection{\code{TealAppDriver$is_visible()}}{ + Test if \code{DOM} elements are visible on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$is_visible( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. A \code{CSS} id will return only one element if the UI is well formed.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Logical vector with all occurrences of the selector. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -Logical vector with all occurrences of the selector. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_visible}{}}} -\subsection{Method \code{expect_visible()}}{ -Expect that \code{DOM} elements are visible on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_visible( +\subsection{\code{TealAppDriver$expect_visible()}}{ + Expect that \code{DOM} elements are visible on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_visible( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE, timeout -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. if more than one element is found, at least one must be visible for this expectation to be successful.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - -\item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. + \item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. Defaults to the \code{timeout} parameter set during initialization of the \code{TealAppDriver} object.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_hidden}{}}} -\subsection{Method \code{expect_hidden()}}{ -Expect that \code{DOM} elements are hidden on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_hidden( +\subsection{\code{TealAppDriver$expect_hidden()}}{ + Expect that \code{DOM} elements are hidden on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_hidden( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE, timeout -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. if more than one element is found, all of them must be invisible for this expectation to be successful.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - -\item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. + \item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. Defaults to the \code{timeout} parameter set during initialization of the \code{TealAppDriver} object.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_data_filters}{}}} -\subsection{Method \code{get_active_data_filters()}}{ -Get the active filter variables from a dataset in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_data_filters(dataset_name = NULL)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to get the filter variables from. +\subsection{\code{TealAppDriver$get_active_data_filters()}}{ + Get the active filter variables from a dataset in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_data_filters(dataset_name = NULL)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to get the filter variables from. If \code{NULL}, the filter variables for all the datasets will be returned in a list.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-add_filter_var}{}}} -\subsection{Method \code{add_filter_var()}}{ -Add a new variable from the dataset to be filtered. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$add_filter_var(dataset_name, var_name, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$add_filter_var()}}{ + Add a new variable from the dataset to be filtered. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$add_filter_var(dataset_name, var_name, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to add the filter variable to.} + \item{\code{var_name}}{(character) The name of the variable to add to the filter panel.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to add the filter variable to.} - -\item{\code{var_name}}{(character) The name of the variable to add to the filter panel.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-remove_filter_var}{}}} -\subsection{Method \code{remove_filter_var()}}{ -Remove an active filter variable of a dataset from the active filter variables panel. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$remove_filter_var(dataset_name = NULL, var_name = NULL)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to remove the filter variable from. +\subsection{\code{TealAppDriver$remove_filter_var()}}{ + Remove an active filter variable of a dataset from the active filter variables panel. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$remove_filter_var(dataset_name = NULL, var_name = NULL)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to remove the filter variable from. If \code{NULL}, all the filter variables will be removed.} - -\item{\code{var_name}}{(character) The name of the variable to remove from the filter panel. + \item{\code{var_name}}{(character) The name of the variable to remove from the filter panel. If \code{NULL}, all the filter variables of the dataset will be removed.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_active_filter_selection}{}}} -\subsection{Method \code{set_active_filter_selection()}}{ -Set the active filter values for a variable of a dataset in the active filter variable panel. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_active_filter_selection(dataset_name, var_name, input, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$set_active_filter_selection()}}{ + Set the active filter values for a variable of a dataset in the active filter variable panel. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_active_filter_selection(dataset_name, var_name, input, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to set the filter value for.} + \item{\code{var_name}}{(character) The name of the variable to set the filter value for.} + \item{\code{input}}{The value to set the filter to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to set the filter value for.} - -\item{\code{var_name}}{(character) The name of the variable to set the filter value for.} - -\item{\code{input}}{The value to set the filter to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_attr}{}}} -\subsection{Method \code{get_attr()}}{ -Extract \code{html} attribute (found by a \code{selector}). -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_attr(selector, attribute)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_attr()}}{ + Extract \code{html} attribute (found by a \code{selector}). + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_attr(selector, attribute)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) specifying the selector to be used to get the content of a specific node.} + \item{\code{attribute}}{(\code{character(1)}) name of an attribute to retrieve from a node specified by \code{selector}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{character} vector. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) specifying the selector to be used to get the content of a specific node.} - -\item{\code{attribute}}{(\code{character(1)}) name of an attribute to retrieve from a node specified by \code{selector}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{character} vector. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_html_rvest}{}}} -\subsection{Method \code{get_html_rvest()}}{ -Wrapper around \code{get_html} that passes the output directly to \code{rvest::read_html}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_html_rvest(selector)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{\code{(character(1))} passed to \code{get_html}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -An XML document. +\subsection{\code{TealAppDriver$get_html_rvest()}}{ + Wrapper around \code{get_html} that passes the output directly to \code{rvest::read_html}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_html_rvest(selector)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{\code{(character(1))} passed to \code{get_html}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + An XML document. Wrapper around \code{get_url()} method that opens the app in the browser. + } } -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-open_url}{}}} -\subsection{Method \code{open_url()}}{ -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$open_url()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$open_url()}}{ + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$open_url()} + \if{html}{\out{
}} + } + \subsection{Returns}{ + Nothing. Opens the underlying teal app in the browser. + } } -\subsection{Returns}{ -Nothing. Opens the underlying teal app in the browser. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-wait_for_active_module_value}{}}} -\subsection{Method \code{wait_for_active_module_value()}}{ -Waits until a specified input, output, or export value. +\subsection{\code{TealAppDriver$wait_for_active_module_value()}}{ + Waits until a specified input, output, or export value. This function serves as a wrapper around the \code{wait_for_value} method, providing a more flexible interface for waiting on different types of values within the active module namespace. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$wait_for_active_module_value( + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$wait_for_active_module_value( input = rlang::missing_arg(), output = rlang::missing_arg(), export = rlang::missing_arg(), ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input, output, export}}{A name of an input, output, or export value. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input, output, export}}{A name of an input, output, or export value. Only one of these parameters may be used.} - -\item{\code{...}}{Must be empty. Allows for parameter expansion. + \item{\code{...}}{Must be empty. Allows for parameter expansion. Parameter with additional value to passed in \code{wait_for_value}.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + } diff --git a/man/TealReportCard.Rd b/man/TealReportCard.Rd index 8067c8002b..c7aee7a256 100644 --- a/man/TealReportCard.Rd +++ b/man/TealReportCard.Rd @@ -13,7 +13,7 @@ meta data. \examples{ ## ------------------------------------------------ -## Method `TealReportCard$append_src` +## Method `TealReportCard$append_src()` ## ------------------------------------------------ card <- TealReportCard$new()$append_src( @@ -22,7 +22,7 @@ card <- TealReportCard$new()$append_src( card$get_content()[[1]] ## ------------------------------------------------ -## Method `TealReportCard$append_encodings` +## Method `TealReportCard$append_encodings()` ## ------------------------------------------------ card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) @@ -34,137 +34,137 @@ card$get_content()[[1]] } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-TealReportCard-append_src}{\code{TealReportCard$append_src()}} -\item \href{#method-TealReportCard-append_fs}{\code{TealReportCard$append_fs()}} -\item \href{#method-TealReportCard-append_encodings}{\code{TealReportCard$append_encodings()}} -\item \href{#method-TealReportCard-clone}{\code{TealReportCard$clone()}} -} -} -\if{html}{\out{ -
Inherited methods + \itemize{ + \item \href{#method-TealReportCard-append_src}{\code{TealReportCard$append_src()}} + \item \href{#method-TealReportCard-append_fs}{\code{TealReportCard$append_fs()}} + \item \href{#method-TealReportCard-append_encodings}{\code{TealReportCard$append_encodings()}} + \item \href{#method-TealReportCard-clone}{\code{TealReportCard$clone()}} + } +} +\if{html}{\out{
Inherited methods -
-}} +
}} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_src}{}}} -\subsection{Method \code{append_src()}}{ -Appends the source code to the \code{content} meta data of this \code{TealReportCard}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_src(src, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{src}}{(\code{character(1)}) code as text.} - -\item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. +\subsection{\code{TealReportCard$append_src()}}{ + Appends the source code to the \code{content} meta data of this \code{TealReportCard}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_src(src, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{src}}{(\code{character(1)}) code as text.} + \item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. But \code{eval} parameter is always set to \code{FALSE}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -Object of class \code{TealReportCard}, invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{card <- TealReportCard$new()$append_src( + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Object of class \code{TealReportCard}, invisibly. + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{card <- TealReportCard$new()$append_src( "plot(iris)" ) card$get_content()[[1]] } -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_fs}{}}} -\subsection{Method \code{append_fs()}}{ -Appends the filter state list to the \code{content} and \code{metadata} of this \code{TealReportCard}. +\subsection{\code{TealReportCard$append_fs()}}{ + Appends the filter state list to the \code{content} and \code{metadata} of this \code{TealReportCard}. If the filter state list has an attribute named \code{formatted}, it appends it to the card otherwise it uses the default \code{yaml::as.yaml} to format the list. If the filter state list is empty, nothing is appended to the \code{content}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_fs(fs)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_fs(fs)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{fs}}{(\code{teal_slices}) object returned from \code{\link[=teal_slices]{teal_slices()}} function.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{self}, invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{fs}}{(\code{teal_slices}) object returned from \code{\link[=teal_slices]{teal_slices()}} function.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{self}, invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_encodings}{}}} -\subsection{Method \code{append_encodings()}}{ -Appends the encodings list to the \code{content} and \code{metadata} of this \code{TealReportCard}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_encodings(encodings)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{self}, invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) +\subsection{\code{TealReportCard$append_encodings()}}{ + Appends the encodings list to the \code{content} and \code{metadata} of this \code{TealReportCard}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_encodings(encodings)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{self}, invisibly. + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) card$get_content()[[1]] - } -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{TealReportCard$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/dot-teal_favicon.Rd b/man/dot-teal_favicon.Rd index 9f20d66d8c..5c55019bfd 100644 --- a/man/dot-teal_favicon.Rd +++ b/man/dot-teal_favicon.Rd @@ -1,12 +1,8 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R -\docType{data} \name{.teal_favicon} \alias{.teal_favicon} \title{The default favicon for the teal app.} -\format{ -An object of class \code{character} of length 1. -} \usage{ .teal_favicon } diff --git a/man/reporter_previewer_module.Rd b/man/reporter_previewer_module.Rd index 6f0ff86354..202a7838f7 100644 --- a/man/reporter_previewer_module.Rd +++ b/man/reporter_previewer_module.Rd @@ -24,8 +24,8 @@ It is now deprecated in favor of the options: \itemize{ \item \code{teal.reporter.nav_buttons = c("preview", "download", "load", "reset")} to control which buttons will be displayed in the drop-down. -\item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} -\item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} -\item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} } } diff --git a/man/teal-package.Rd b/man/teal-package.Rd index 49f09ba63c..27e798d529 100644 --- a/man/teal-package.Rd +++ b/man/teal-package.Rd @@ -27,6 +27,7 @@ Useful links: Authors: \itemize{ + \item Dawid Kaledkowski \email{dawid.kaledkowski@roche.com} (\href{https://orcid.org/0000-0001-9533-457X}{ORCID}) \item Pawel Rucki \email{pawel.rucki@roche.com} \item Aleksander Chlebowski \email{aleksander.chlebowski@contractors.roche.com} (\href{https://orcid.org/0000-0001-5018-6294}{ORCID}) \item Andre Verissimo \email{andre.verissimo@roche.com} (\href{https://orcid.org/0000-0002-2212-339X}{ORCID}) From 5d2652313fe29c87a2619153266d17181039c687 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 19 May 2026 00:05:19 +0200 Subject: [PATCH 31/33] lintr and roxy --- R/validate_inputs.R | 4 +- man/TealAppDriver.Rd | 950 +++++++++--------- man/TealReportCard.Rd | 206 ++-- man/dot-teal_favicon.Rd | 4 - man/reporter_previewer_module.Rd | 6 +- man/teal-package.Rd | 1 + man/validate_input.Rd | 3 +- .../test-shinytest2-teal_data_module.R | 2 +- 8 files changed, 599 insertions(+), 577 deletions(-) diff --git a/R/validate_inputs.R b/R/validate_inputs.R index da179fd9cd..2640ab237d 100644 --- a/R/validate_inputs.R +++ b/R/validate_inputs.R @@ -297,7 +297,8 @@ validate_input <- function(inputId, # nolint #' @details #' - `need_input()`: Validate a Shiny input and returns a message to be used in a [`shiny::validate()`] call. #' The message is sent to the client and appears both in the input widget. -#' To observe the message in the output element, `need_input()` should be called inside a `shiny::validate()` call, e.g. via `validate(validate_input(...))`. +#' To observe the message in the output element, `need_input()` should be called inside a +#' `shiny::validate()` call, e.g. via `validate(validate_input(...))`. #' @export #' @examples #' my_module <- module( @@ -394,6 +395,5 @@ need_input <- function(inputId, # nolint #' @export use_validate_input_js <- function() { addResourcePath("js", system.file("js", package = "teal")) - # system.file("js", "input-validator.js", package = "teal")) singleton(tags$script(src = "js/input-validator.js")) } diff --git a/man/TealAppDriver.Rd b/man/TealAppDriver.Rd index 7435125985..ae28654951 100644 --- a/man/TealAppDriver.Rd +++ b/man/TealAppDriver.Rd @@ -4,11 +4,6 @@ \alias{TealAppDriver} \title{Drive a \code{teal} application} \description{ -Drive a \code{teal} application - -Drive a \code{teal} application -} -\details{ Extension of the \code{shinytest2::AppDriver} class with methods for driving a teal application for performing interactions for \code{shinytest2} tests. } @@ -18,652 +13,681 @@ driving a teal application for performing interactions for \code{shinytest2} tes } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-TealAppDriver-new}{\code{TealAppDriver$new()}} -\item \href{#method-TealAppDriver-stop}{\code{TealAppDriver$stop()}} -\item \href{#method-TealAppDriver-click}{\code{TealAppDriver$click()}} -\item \href{#method-TealAppDriver-expect_no_shiny_error}{\code{TealAppDriver$expect_no_shiny_error()}} -\item \href{#method-TealAppDriver-expect_no_validation_error}{\code{TealAppDriver$expect_no_validation_error()}} -\item \href{#method-TealAppDriver-expect_validation_error}{\code{TealAppDriver$expect_validation_error()}} -\item \href{#method-TealAppDriver-set_input}{\code{TealAppDriver$set_input()}} -\item \href{#method-TealAppDriver-navigate_teal_tab}{\code{TealAppDriver$navigate_teal_tab()}} -\item \href{#method-TealAppDriver-namespaces}{\code{TealAppDriver$namespaces()}} -\item \href{#method-TealAppDriver-get_active_module_input}{\code{TealAppDriver$get_active_module_input()}} -\item \href{#method-TealAppDriver-get_active_module_output}{\code{TealAppDriver$get_active_module_output()}} -\item \href{#method-TealAppDriver-get_active_module_table_output}{\code{TealAppDriver$get_active_module_table_output()}} -\item \href{#method-TealAppDriver-get_active_module_plot_output}{\code{TealAppDriver$get_active_module_plot_output()}} -\item \href{#method-TealAppDriver-set_active_module_input}{\code{TealAppDriver$set_active_module_input()}} -\item \href{#method-TealAppDriver-get_active_filter_vars}{\code{TealAppDriver$get_active_filter_vars()}} -\item \href{#method-TealAppDriver-get_active_data_summary_table}{\code{TealAppDriver$get_active_data_summary_table()}} -\item \href{#method-TealAppDriver-is_visible}{\code{TealAppDriver$is_visible()}} -\item \href{#method-TealAppDriver-expect_visible}{\code{TealAppDriver$expect_visible()}} -\item \href{#method-TealAppDriver-expect_hidden}{\code{TealAppDriver$expect_hidden()}} -\item \href{#method-TealAppDriver-get_active_data_filters}{\code{TealAppDriver$get_active_data_filters()}} -\item \href{#method-TealAppDriver-add_filter_var}{\code{TealAppDriver$add_filter_var()}} -\item \href{#method-TealAppDriver-remove_filter_var}{\code{TealAppDriver$remove_filter_var()}} -\item \href{#method-TealAppDriver-set_active_filter_selection}{\code{TealAppDriver$set_active_filter_selection()}} -\item \href{#method-TealAppDriver-get_attr}{\code{TealAppDriver$get_attr()}} -\item \href{#method-TealAppDriver-get_html_rvest}{\code{TealAppDriver$get_html_rvest()}} -\item \href{#method-TealAppDriver-open_url}{\code{TealAppDriver$open_url()}} -\item \href{#method-TealAppDriver-wait_for_active_module_value}{\code{TealAppDriver$wait_for_active_module_value()}} -} -} -\if{html}{\out{ -
Inherited methods + \itemize{ + \item \href{#method-TealAppDriver-initialize}{\code{TealAppDriver$new()}} + \item \href{#method-TealAppDriver-stop}{\code{TealAppDriver$stop()}} + \item \href{#method-TealAppDriver-click}{\code{TealAppDriver$click()}} + \item \href{#method-TealAppDriver-expect_no_shiny_error}{\code{TealAppDriver$expect_no_shiny_error()}} + \item \href{#method-TealAppDriver-expect_no_validation_error}{\code{TealAppDriver$expect_no_validation_error()}} + \item \href{#method-TealAppDriver-expect_validation_error}{\code{TealAppDriver$expect_validation_error()}} + \item \href{#method-TealAppDriver-set_input}{\code{TealAppDriver$set_input()}} + \item \href{#method-TealAppDriver-navigate_teal_tab}{\code{TealAppDriver$navigate_teal_tab()}} + \item \href{#method-TealAppDriver-namespaces}{\code{TealAppDriver$namespaces()}} + \item \href{#method-TealAppDriver-get_active_module_input}{\code{TealAppDriver$get_active_module_input()}} + \item \href{#method-TealAppDriver-get_active_module_output}{\code{TealAppDriver$get_active_module_output()}} + \item \href{#method-TealAppDriver-get_active_module_table_output}{\code{TealAppDriver$get_active_module_table_output()}} + \item \href{#method-TealAppDriver-get_active_module_plot_output}{\code{TealAppDriver$get_active_module_plot_output()}} + \item \href{#method-TealAppDriver-set_active_module_input}{\code{TealAppDriver$set_active_module_input()}} + \item \href{#method-TealAppDriver-get_active_filter_vars}{\code{TealAppDriver$get_active_filter_vars()}} + \item \href{#method-TealAppDriver-get_active_data_summary_table}{\code{TealAppDriver$get_active_data_summary_table()}} + \item \href{#method-TealAppDriver-is_visible}{\code{TealAppDriver$is_visible()}} + \item \href{#method-TealAppDriver-expect_visible}{\code{TealAppDriver$expect_visible()}} + \item \href{#method-TealAppDriver-expect_hidden}{\code{TealAppDriver$expect_hidden()}} + \item \href{#method-TealAppDriver-get_active_data_filters}{\code{TealAppDriver$get_active_data_filters()}} + \item \href{#method-TealAppDriver-add_filter_var}{\code{TealAppDriver$add_filter_var()}} + \item \href{#method-TealAppDriver-remove_filter_var}{\code{TealAppDriver$remove_filter_var()}} + \item \href{#method-TealAppDriver-set_active_filter_selection}{\code{TealAppDriver$set_active_filter_selection()}} + \item \href{#method-TealAppDriver-get_attr}{\code{TealAppDriver$get_attr()}} + \item \href{#method-TealAppDriver-get_html_rvest}{\code{TealAppDriver$get_html_rvest()}} + \item \href{#method-TealAppDriver-open_url}{\code{TealAppDriver$open_url()}} + \item \href{#method-TealAppDriver-wait_for_active_module_value}{\code{TealAppDriver$wait_for_active_module_value()}} + } +} +\if{html}{\out{
Inherited methods -
-}} +
}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-TealAppDriver-new}{}}} -\subsection{Method \code{new()}}{ -Initialize a \code{TealAppDriver} object for testing a \code{teal} application. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$new( +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TealAppDriver-initialize}{}}} +\subsection{\code{TealAppDriver$new()}}{ + Initialize a \code{TealAppDriver} object for testing a \code{teal} application. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$new( app, options = list(), teal_options = list(), timeout = rlang::missing_arg(), load_timeout = rlang::missing_arg(), ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{app}}{(\code{teal_app})} - -\item{\code{options}}{(\code{list}) passed to \code{shinyApp(options)}. See \code{\link[shiny:shinyApp]{shiny::shinyApp()}}.} - -\item{\code{teal_options}}{(\code{list}) named list of R options to set inside the spawned +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{app}}{(\code{teal_app})} + \item{\code{options}}{(\code{list}) passed to \code{shinyApp(options)}. See \code{\link[shiny:shinyApp]{shiny::shinyApp()}}.} + \item{\code{teal_options}}{(\code{list}) named list of R options to set inside the spawned Shiny process (e.g. \code{list(teal.enable_deep_linking = TRUE)}). Applied at session start so option-gated server logic sees them.} - -\item{\code{timeout}}{(\code{numeric}) Default number of milliseconds for any timeout or + \item{\code{timeout}}{(\code{numeric}) Default number of milliseconds for any timeout or timeout_ parameter in the \code{TealAppDriver} class. Defaults to 20s. See \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{new} method for more details on how to change it via options or environment variables.} - -\item{\code{load_timeout}}{(\code{numeric}) How long to wait for the app to load, in ms. + \item{\code{load_timeout}}{(\code{numeric}) How long to wait for the app to load, in ms. This includes the time to start R. Defaults to 100s. See \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{new} method for more details on how to change it via options or environment variables} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$new}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -Object of class \code{TealAppDriver} -} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$new}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Object of class \code{TealAppDriver} + } } + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-stop}{}}} -\subsection{Method \code{stop()}}{ -Extension of the parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{stop} method that prints the logs +\subsection{\code{TealAppDriver$stop()}}{ + Extension of the parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{stop} method that prints the logs if the \code{ACTIONS_STEP_DEBUG} environment variable is set to \code{true} (case of value is ignored). -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$stop(...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$stop(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-click}{}}} -\subsection{Method \code{click()}}{ -Append parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click} method with a call to \code{waif_for_idle()} method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$click(...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$click()}}{ + Append parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click} method with a call to \code{waif_for_idle()} method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$click(...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{...}}{arguments passed to parent \code{\link[shinytest2:AppDriver]{shinytest2::AppDriver}} \code{click()} method.} -} -\if{html}{\out{
}} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_no_shiny_error}{}}} -\subsection{Method \code{expect_no_shiny_error()}}{ -Check if the app has shiny errors. This checks for global shiny errors. +\subsection{\code{TealAppDriver$expect_no_shiny_error()}}{ + Check if the app has shiny errors. This checks for global shiny errors. Note that any shiny errors dependent on shiny server render will only be captured after the teal module tab is visited because shiny will not trigger server computations when the tab is invisible. So, navigate to the module tab you want to test before calling this function. Although, this catches errors hidden in the other module tabs if they are already rendered. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_no_shiny_error()}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_no_shiny_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_no_validation_error}{}}} -\subsection{Method \code{expect_no_validation_error()}}{ -Check if the app has no validation errors. This checks for global shiny validation errors. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_no_validation_error()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$expect_no_validation_error()}}{ + Check if the app has no validation errors. This checks for global shiny validation errors. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_no_validation_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_validation_error}{}}} -\subsection{Method \code{expect_validation_error()}}{ -Check if the app has validation errors. This checks for global shiny validation errors. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_validation_error()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$expect_validation_error()}}{ + Check if the app has validation errors. This checks for global shiny validation errors. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_validation_error()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_input}{}}} -\subsection{Method \code{set_input()}}{ -Set the input in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_input(input_id, value, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$set_input()}}{ + Set the input in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_input(input_id, value, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id with it's complete name space.} + \item{\code{value}}{The value to set the input to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id with it's complete name space.} - -\item{\code{value}}{The value to set the input to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-navigate_teal_tab}{}}} -\subsection{Method \code{navigate_teal_tab()}}{ -Navigate the teal tabs in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$navigate_teal_tab(tab)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{tab}}{(character) Labels of tabs to navigate to. +\subsection{\code{TealAppDriver$navigate_teal_tab()}}{ + Navigate the teal tabs in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$navigate_teal_tab(tab)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{tab}}{(character) Labels of tabs to navigate to. Note: Make sure to provide unique labels for the tabs.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-namespaces}{}}} -\subsection{Method \code{namespaces()}}{ -\code{NS} in different sections of \code{teal} app -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$namespaces(is_selector = FALSE)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$namespaces()}}{ + \code{NS} in different sections of \code{teal} app + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$namespaces(is_selector = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{is_selector}}{(\code{logical(1)}) whether \code{ns} function should prefix with \verb{#}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + list of \code{ns}. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{is_selector}}{(\code{logical(1)}) whether \code{ns} function should prefix with \verb{#}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -list of \code{ns}. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_input}{}}} -\subsection{Method \code{get_active_module_input()}}{ -Get the input from the module in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_input()}}{ + Get the input from the module in the \code{teal} app. This function will only access inputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_input(input_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_input(input_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id to get the value from.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The value of the shiny input. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id to get the value from.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The value of the shiny input. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_output}{}}} -\subsection{Method \code{get_active_module_output()}}{ -Get the output from the module in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_output()}}{ + Get the output from the module in the \code{teal} app. This function will only access outputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_output(output_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_output(output_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{output_id}}{(character) The shiny output id to get the value from.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The value of the shiny output. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{output_id}}{(character) The shiny output id to get the value from.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The value of the shiny output. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_table_output}{}}} -\subsection{Method \code{get_active_module_table_output()}}{ -Get the output from the module's \code{teal.widgets::table_with_settings} or \code{DT::DTOutput} in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_table_output()}}{ + Get the output from the module's \code{teal.widgets::table_with_settings} or \code{DT::DTOutput} in the \code{teal} app. This function will only access outputs from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_table_output(table_id, which = 1)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{table_id}}{(\code{character(1)}) The id of the table in the active teal module's name space.} - -\item{\code{which}}{(integer) If there is more than one table, which should be extracted. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_table_output(table_id, which = 1)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{table_id}}{(\code{character(1)}) The id of the table in the active teal module's name space.} + \item{\code{which}}{(integer) If there is more than one table, which should be extracted. By default it will look for a table that is built using \code{teal.widgets::table_with_settings}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The data.frame with table contents. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The data.frame with table contents. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_module_plot_output}{}}} -\subsection{Method \code{get_active_module_plot_output()}}{ -Get the output from the module's \code{teal.widgets::plot_with_settings} in the \code{teal} app. +\subsection{\code{TealAppDriver$get_active_module_plot_output()}}{ + Get the output from the module's \code{teal.widgets::plot_with_settings} in the \code{teal} app. This function will only access plots from the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_module_plot_output(plot_id)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_module_plot_output(plot_id)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{plot_id}}{(\code{character(1)}) The id of the plot in the active teal module's name space.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{src} attribute as \code{character(1)} vector. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{plot_id}}{(\code{character(1)}) The id of the plot in the active teal module's name space.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{src} attribute as \code{character(1)} vector. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_active_module_input}{}}} -\subsection{Method \code{set_active_module_input()}}{ -Set the input in the module in the \code{teal} app. +\subsection{\code{TealAppDriver$set_active_module_input()}}{ + Set the input in the module in the \code{teal} app. This function will only set inputs in the name space of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_active_module_input(input_id, value, ...)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_active_module_input(input_id, value, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input_id}}{(character) The shiny input id to get the value from.} + \item{\code{value}}{The value to set the input to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input_id}}{(character) The shiny input id to get the value from.} - -\item{\code{value}}{The value to set the input to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_filter_vars}{}}} -\subsection{Method \code{get_active_filter_vars()}}{ -Get the active datasets that can be accessed via the filter panel of the current active teal module. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_filter_vars()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_active_filter_vars()}}{ + Get the active datasets that can be accessed via the filter panel of the current active teal module. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_filter_vars()} + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_data_summary_table}{}}} -\subsection{Method \code{get_active_data_summary_table()}}{ -Get the active data summary table -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_data_summary_table()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_active_data_summary_table()}}{ + Get the active data summary table + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_data_summary_table()} + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{data.frame} + } } -\subsection{Returns}{ -\code{data.frame} -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-is_visible}{}}} -\subsection{Method \code{is_visible()}}{ -Test if \code{DOM} elements are visible on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$is_visible( +\subsection{\code{TealAppDriver$is_visible()}}{ + Test if \code{DOM} elements are visible on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$is_visible( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. A \code{CSS} id will return only one element if the UI is well formed.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Logical vector with all occurrences of the selector. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -Logical vector with all occurrences of the selector. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_visible}{}}} -\subsection{Method \code{expect_visible()}}{ -Expect that \code{DOM} elements are visible on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_visible( +\subsection{\code{TealAppDriver$expect_visible()}}{ + Expect that \code{DOM} elements are visible on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_visible( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE, timeout -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. if more than one element is found, at least one must be visible for this expectation to be successful.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - -\item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. + \item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. Defaults to the \code{timeout} parameter set during initialization of the \code{TealAppDriver} object.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-expect_hidden}{}}} -\subsection{Method \code{expect_hidden()}}{ -Expect that \code{DOM} elements are hidden on the page with a JavaScript call. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$expect_hidden( +\subsection{\code{TealAppDriver$expect_hidden()}}{ + Expect that \code{DOM} elements are hidden on the page with a JavaScript call. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$expect_hidden( selector, content_visibility_auto = FALSE, opacity_property = FALSE, visibility_property = FALSE, timeout -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) \code{CSS} selector to check visibility. if more than one element is found, all of them must be invisible for this expectation to be successful.} - -\item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) + \item{\code{content_visibility_auto, opacity_property, visibility_property}}{(\code{logical(1)}) See more information on \url{https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility}.} - -\item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. + \item{\code{timeout}}{(\code{numeric(1)}) Time in milliseconds to wait for the expectation to be met. Defaults to the \code{timeout} parameter set during initialization of the \code{TealAppDriver} object.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_active_data_filters}{}}} -\subsection{Method \code{get_active_data_filters()}}{ -Get the active filter variables from a dataset in the \code{teal} app. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_active_data_filters(dataset_name = NULL)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to get the filter variables from. +\subsection{\code{TealAppDriver$get_active_data_filters()}}{ + Get the active filter variables from a dataset in the \code{teal} app. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_active_data_filters(dataset_name = NULL)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to get the filter variables from. If \code{NULL}, the filter variables for all the datasets will be returned in a list.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-add_filter_var}{}}} -\subsection{Method \code{add_filter_var()}}{ -Add a new variable from the dataset to be filtered. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$add_filter_var(dataset_name, var_name, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$add_filter_var()}}{ + Add a new variable from the dataset to be filtered. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$add_filter_var(dataset_name, var_name, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to add the filter variable to.} + \item{\code{var_name}}{(character) The name of the variable to add to the filter panel.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to add the filter variable to.} - -\item{\code{var_name}}{(character) The name of the variable to add to the filter panel.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-remove_filter_var}{}}} -\subsection{Method \code{remove_filter_var()}}{ -Remove an active filter variable of a dataset from the active filter variables panel. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$remove_filter_var(dataset_name = NULL, var_name = NULL)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to remove the filter variable from. +\subsection{\code{TealAppDriver$remove_filter_var()}}{ + Remove an active filter variable of a dataset from the active filter variables panel. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$remove_filter_var(dataset_name = NULL, var_name = NULL)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to remove the filter variable from. If \code{NULL}, all the filter variables will be removed.} - -\item{\code{var_name}}{(character) The name of the variable to remove from the filter panel. + \item{\code{var_name}}{(character) The name of the variable to remove from the filter panel. If \code{NULL}, all the filter variables of the dataset will be removed.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-set_active_filter_selection}{}}} -\subsection{Method \code{set_active_filter_selection()}}{ -Set the active filter values for a variable of a dataset in the active filter variable panel. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$set_active_filter_selection(dataset_name, var_name, input, ...)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$set_active_filter_selection()}}{ + Set the active filter values for a variable of a dataset in the active filter variable panel. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$set_active_filter_selection(dataset_name, var_name, input, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{dataset_name}}{(character) The name of the dataset to set the filter value for.} + \item{\code{var_name}}{(character) The name of the variable to set the filter value for.} + \item{\code{input}}{The value to set the filter to.} + \item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{TealAppDriver} object invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{dataset_name}}{(character) The name of the dataset to set the filter value for.} - -\item{\code{var_name}}{(character) The name of the variable to set the filter value for.} - -\item{\code{input}}{The value to set the filter to.} - -\item{\code{...}}{Additional arguments to be passed to \code{shinytest2::AppDriver$set_inputs}} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{TealAppDriver} object invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_attr}{}}} -\subsection{Method \code{get_attr()}}{ -Extract \code{html} attribute (found by a \code{selector}). -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_attr(selector, attribute)}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$get_attr()}}{ + Extract \code{html} attribute (found by a \code{selector}). + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_attr(selector, attribute)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{(\code{character(1)}) specifying the selector to be used to get the content of a specific node.} + \item{\code{attribute}}{(\code{character(1)}) name of an attribute to retrieve from a node specified by \code{selector}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + The \code{character} vector. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{(\code{character(1)}) specifying the selector to be used to get the content of a specific node.} - -\item{\code{attribute}}{(\code{character(1)}) name of an attribute to retrieve from a node specified by \code{selector}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -The \code{character} vector. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-get_html_rvest}{}}} -\subsection{Method \code{get_html_rvest()}}{ -Wrapper around \code{get_html} that passes the output directly to \code{rvest::read_html}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$get_html_rvest(selector)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{selector}}{\code{(character(1))} passed to \code{get_html}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -An XML document. +\subsection{\code{TealAppDriver$get_html_rvest()}}{ + Wrapper around \code{get_html} that passes the output directly to \code{rvest::read_html}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$get_html_rvest(selector)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{selector}}{\code{(character(1))} passed to \code{get_html}.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + An XML document. Wrapper around \code{get_url()} method that opens the app in the browser. + } } -} + \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-open_url}{}}} -\subsection{Method \code{open_url()}}{ -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$open_url()}\if{html}{\out{
}} +\subsection{\code{TealAppDriver$open_url()}}{ + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$open_url()} + \if{html}{\out{
}} + } + \subsection{Returns}{ + Nothing. Opens the underlying teal app in the browser. + } } -\subsection{Returns}{ -Nothing. Opens the underlying teal app in the browser. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealAppDriver-wait_for_active_module_value}{}}} -\subsection{Method \code{wait_for_active_module_value()}}{ -Waits until a specified input, output, or export value. +\subsection{\code{TealAppDriver$wait_for_active_module_value()}}{ + Waits until a specified input, output, or export value. This function serves as a wrapper around the \code{wait_for_value} method, providing a more flexible interface for waiting on different types of values within the active module namespace. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealAppDriver$wait_for_active_module_value( + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealAppDriver$wait_for_active_module_value( input = rlang::missing_arg(), output = rlang::missing_arg(), export = rlang::missing_arg(), ... -)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{input, output, export}}{A name of an input, output, or export value. +)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{input, output, export}}{A name of an input, output, or export value. Only one of these parameters may be used.} - -\item{\code{...}}{Must be empty. Allows for parameter expansion. + \item{\code{...}}{Must be empty. Allows for parameter expansion. Parameter with additional value to passed in \code{wait_for_value}.} + } + \if{html}{\out{
}} + } } -\if{html}{\out{
}} -} -} + } diff --git a/man/TealReportCard.Rd b/man/TealReportCard.Rd index 8067c8002b..c7aee7a256 100644 --- a/man/TealReportCard.Rd +++ b/man/TealReportCard.Rd @@ -13,7 +13,7 @@ meta data. \examples{ ## ------------------------------------------------ -## Method `TealReportCard$append_src` +## Method `TealReportCard$append_src()` ## ------------------------------------------------ card <- TealReportCard$new()$append_src( @@ -22,7 +22,7 @@ card <- TealReportCard$new()$append_src( card$get_content()[[1]] ## ------------------------------------------------ -## Method `TealReportCard$append_encodings` +## Method `TealReportCard$append_encodings()` ## ------------------------------------------------ card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) @@ -34,137 +34,137 @@ card$get_content()[[1]] } \section{Methods}{ \subsection{Public methods}{ -\itemize{ -\item \href{#method-TealReportCard-append_src}{\code{TealReportCard$append_src()}} -\item \href{#method-TealReportCard-append_fs}{\code{TealReportCard$append_fs()}} -\item \href{#method-TealReportCard-append_encodings}{\code{TealReportCard$append_encodings()}} -\item \href{#method-TealReportCard-clone}{\code{TealReportCard$clone()}} -} -} -\if{html}{\out{ -
Inherited methods + \itemize{ + \item \href{#method-TealReportCard-append_src}{\code{TealReportCard$append_src()}} + \item \href{#method-TealReportCard-append_fs}{\code{TealReportCard$append_fs()}} + \item \href{#method-TealReportCard-append_encodings}{\code{TealReportCard$append_encodings()}} + \item \href{#method-TealReportCard-clone}{\code{TealReportCard$clone()}} + } +} +\if{html}{\out{
Inherited methods -
-}} +
}} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_src}{}}} -\subsection{Method \code{append_src()}}{ -Appends the source code to the \code{content} meta data of this \code{TealReportCard}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_src(src, ...)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{src}}{(\code{character(1)}) code as text.} - -\item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. +\subsection{\code{TealReportCard$append_src()}}{ + Appends the source code to the \code{content} meta data of this \code{TealReportCard}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_src(src, ...)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{src}}{(\code{character(1)}) code as text.} + \item{\code{...}}{any \code{rmarkdown} \code{R} chunk parameter and its value. But \code{eval} parameter is always set to \code{FALSE}.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -Object of class \code{TealReportCard}, invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{card <- TealReportCard$new()$append_src( + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + Object of class \code{TealReportCard}, invisibly. + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{card <- TealReportCard$new()$append_src( "plot(iris)" ) card$get_content()[[1]] } -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_fs}{}}} -\subsection{Method \code{append_fs()}}{ -Appends the filter state list to the \code{content} and \code{metadata} of this \code{TealReportCard}. +\subsection{\code{TealReportCard$append_fs()}}{ + Appends the filter state list to the \code{content} and \code{metadata} of this \code{TealReportCard}. If the filter state list has an attribute named \code{formatted}, it appends it to the card otherwise it uses the default \code{yaml::as.yaml} to format the list. If the filter state list is empty, nothing is appended to the \code{content}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_fs(fs)}\if{html}{\out{
}} + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_fs(fs)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{fs}}{(\code{teal_slices}) object returned from \code{\link[=teal_slices]{teal_slices()}} function.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{self}, invisibly. + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{fs}}{(\code{teal_slices}) object returned from \code{\link[=teal_slices]{teal_slices()}} function.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{self}, invisibly. -} -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-append_encodings}{}}} -\subsection{Method \code{append_encodings()}}{ -Appends the encodings list to the \code{content} and \code{metadata} of this \code{TealReportCard}. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$append_encodings(encodings)}\if{html}{\out{
}} -} - -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} -} -\if{html}{\out{
}} -} -\subsection{Returns}{ -\code{self}, invisibly. -} -\subsection{Examples}{ -\if{html}{\out{
}} -\preformatted{card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) +\subsection{\code{TealReportCard$append_encodings()}}{ + Appends the encodings list to the \code{content} and \code{metadata} of this \code{TealReportCard}. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$append_encodings(encodings)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{encodings}}{(\code{list}) list of encodings selections of the \code{teal} app.} + } + \if{html}{\out{
}} + } + \subsection{Returns}{ + \code{self}, invisibly. + } + \subsection{Examples}{ + \if{html}{\out{
}} + \preformatted{card <- TealReportCard$new()$append_encodings(list(variable1 = "X")) card$get_content()[[1]] - } -\if{html}{\out{
}} - + \if{html}{\out{
}} + } } -} \if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-TealReportCard-clone}{}}} -\subsection{Method \code{clone()}}{ -The objects of this class are cloneable with this method. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{TealReportCard$clone(deep = FALSE)}\if{html}{\out{
}} +\subsection{\code{TealReportCard$clone()}}{ + The objects of this class are cloneable with this method. + \subsection{Usage}{ + \if{html}{\out{
}} + \preformatted{TealReportCard$clone(deep = FALSE)} + \if{html}{\out{
}} + } + \subsection{Arguments}{ + \if{html}{\out{
}} + \describe{ + \item{\code{deep}}{Whether to make a deep clone.} + } + \if{html}{\out{
}} + } } -\subsection{Arguments}{ -\if{html}{\out{
}} -\describe{ -\item{\code{deep}}{Whether to make a deep clone.} -} -\if{html}{\out{
}} -} -} } diff --git a/man/dot-teal_favicon.Rd b/man/dot-teal_favicon.Rd index 9f20d66d8c..5c55019bfd 100644 --- a/man/dot-teal_favicon.Rd +++ b/man/dot-teal_favicon.Rd @@ -1,12 +1,8 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R -\docType{data} \name{.teal_favicon} \alias{.teal_favicon} \title{The default favicon for the teal app.} -\format{ -An object of class \code{character} of length 1. -} \usage{ .teal_favicon } diff --git a/man/reporter_previewer_module.Rd b/man/reporter_previewer_module.Rd index 6f0ff86354..202a7838f7 100644 --- a/man/reporter_previewer_module.Rd +++ b/man/reporter_previewer_module.Rd @@ -24,8 +24,8 @@ It is now deprecated in favor of the options: \itemize{ \item \code{teal.reporter.nav_buttons = c("preview", "download", "load", "reset")} to control which buttons will be displayed in the drop-down. -\item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} -\item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} -\item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} +\item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button_srv]{teal.reporter::download_report_button_srv()}} } } diff --git a/man/teal-package.Rd b/man/teal-package.Rd index 49f09ba63c..27e798d529 100644 --- a/man/teal-package.Rd +++ b/man/teal-package.Rd @@ -27,6 +27,7 @@ Useful links: Authors: \itemize{ + \item Dawid Kaledkowski \email{dawid.kaledkowski@roche.com} (\href{https://orcid.org/0000-0001-9533-457X}{ORCID}) \item Pawel Rucki \email{pawel.rucki@roche.com} \item Aleksander Chlebowski \email{aleksander.chlebowski@contractors.roche.com} (\href{https://orcid.org/0000-0001-5018-6294}{ORCID}) \item Andre Verissimo \email{andre.verissimo@roche.com} (\href{https://orcid.org/0000-0002-2212-339X}{ORCID}) diff --git a/man/validate_input.Rd b/man/validate_input.Rd index 12f8597354..5e2b6be2ed 100644 --- a/man/validate_input.Rd +++ b/man/validate_input.Rd @@ -52,7 +52,8 @@ that calls \code{validate_input}. \itemize{ \item \code{need_input()}: Validate a Shiny input and returns a message to be used in a \code{\link[shiny:validate]{shiny::validate()}} call. The message is sent to the client and appears both in the input widget. -To observe the message in the output element, \code{need_input()} should be called inside a \code{shiny::validate()} call, e.g. via \code{validate(validate_input(...))}. +To observe the message in the output element, \code{need_input()} should be called inside a +\code{shiny::validate()} call, e.g. via \code{validate(validate_input(...))}. } \itemize{ diff --git a/tests/testthat/test-shinytest2-teal_data_module.R b/tests/testthat/test-shinytest2-teal_data_module.R index 67b4fb4a48..cc649bd895 100644 --- a/tests/testthat/test-shinytest2-teal_data_module.R +++ b/tests/testthat/test-shinytest2-teal_data_module.R @@ -110,7 +110,7 @@ testthat::test_that("e2e: teal_data_module auto-closes modal when `once=FALSE` a app$stop() }) -testthat::test_that("e2e: teal_data_module modal stays visible on startup when `once=FALSE` and submit button is present", { +testthat::test_that("e2e: teal_data_module modal stays visible on startup when `once=FALSE` and `need_submit = TRUE`", { skip_if_too_deep(5) app <- TealAppDriver$new( init( From 7f29d21b82138bac9d235734777f0734d0e22703 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 19 May 2026 10:33:13 +0200 Subject: [PATCH 32/33] snapshot manager disabled --- NEWS.md | 1 + R/module_snapshot_manager.R | 13 ++++++++++++ R/zzz.R | 1 + man/module_snapshot_manager.Rd | 9 +++++++++ tests/testthat/test-module_teal.R | 20 +++++++++++++++++++ .../test-shinytest2-teal_data_module.R | 18 ----------------- vignettes/teal-options.Rmd | 6 ++++++ 7 files changed, 50 insertions(+), 18 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2418ce1214..28132be5ac 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ * Improved print method for `teal_module` object (#1685). * Added opt-in URL-based module navigation, controlled by `options(teal.enable_deep_linking = TRUE)` (default: `FALSE`). When enabled, the active module is reflected in the URL as `?active_module=`, and navigating to such a URL or using the browser's back/forward buttons switches to the corresponding tab (#1699). * Exported new utility functions to support module decorators: `srv_transform_teal_data()`, `ui_transform_teal_data()` and `check_decorators()` (#1697). +* Added `teal.snapshot_manager.enable` option (default: `TRUE`) to control whether the Snapshot Manager panel is rendered. Setting it to `FALSE` hides the panel and skips its server logic. # teal 1.1.0 diff --git a/R/module_snapshot_manager.R b/R/module_snapshot_manager.R index 1060774f1e..831bb9dca9 100644 --- a/R/module_snapshot_manager.R +++ b/R/module_snapshot_manager.R @@ -71,6 +71,13 @@ #' (The snapshot will be retrieved by `module_teal` in order to set initial app state in a restored app.) #' Then that snapshot, and the previous snapshot history are dumped into the `values.rds` file in ``. #' +#' @section Disabling the snapshot manager: +#' The snapshot manager can be turned off application-wide by setting the +#' `teal.snapshot_manager.enable` option to `FALSE`. When disabled, both +#' `ui_snapshot_manager_panel()` and `srv_snapshot_manager_panel()` return `NULL`, +#' so the camera icon is not rendered and no snapshot logic is attached. +#' The option defaults to `TRUE` and is set when the `teal` package is loaded. +#' #' @param id (`character(1)`) `shiny` module instance id. #' @param slices_global (`reactiveVal`) that contains a `teal_slices` object #' containing all `teal_slice`s existing in the app, both active and inactive. @@ -86,6 +93,9 @@ NULL #' @rdname module_snapshot_manager ui_snapshot_manager_panel <- function(id) { + if (!isTRUE(getOption("teal.snapshot_manager.enable", TRUE))) { + return(NULL) + } ns <- NS(id) .expand_button( id = ns("show_snapshot_manager"), @@ -96,6 +106,9 @@ ui_snapshot_manager_panel <- function(id) { #' @rdname module_snapshot_manager srv_snapshot_manager_panel <- function(id, slices_global) { + if (!isTRUE(getOption("teal.snapshot_manager.enable", TRUE))) { + return(NULL) + } moduleServer(id, function(input, output, session) { logger::log_debug("srv_snapshot_manager_panel initializing") setBookmarkExclude(c("show_snapshot_manager")) diff --git a/R/zzz.R b/R/zzz.R index 6db7bca13e..88d10863d8 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -9,6 +9,7 @@ teal.sidebar.width = 250, teal.reporter.nav_buttons = c("preview", "download", "load", "reset"), teal.show_src = TRUE, + teal.snapshot_manager.enable = TRUE, teal.bs_theme = .default_teal_bslib_theming ) diff --git a/man/module_snapshot_manager.Rd b/man/module_snapshot_manager.Rd index 723fcd3ccb..f3a34c9b0f 100644 --- a/man/module_snapshot_manager.Rd +++ b/man/module_snapshot_manager.Rd @@ -106,6 +106,15 @@ This is done on the app session, not the module session. Then that snapshot, and the previous snapshot history are dumped into the \code{values.rds} file in \verb{}. } +\section{Disabling the snapshot manager}{ + +The snapshot manager can be turned off application-wide by setting the +\code{teal.snapshot_manager.enable} option to \code{FALSE}. When disabled, both +\code{ui_snapshot_manager_panel()} and \code{srv_snapshot_manager_panel()} return \code{NULL}, +so the camera icon is not rendered and no snapshot logic is attached. +The option defaults to \code{TRUE} and is set when the \code{teal} package is loaded. +} + \author{ Aleksander Chlebowski } diff --git a/tests/testthat/test-module_teal.R b/tests/testthat/test-module_teal.R index 6bea6a044a..6ead8bd149 100644 --- a/tests/testthat/test-module_teal.R +++ b/tests/testthat/test-module_teal.R @@ -3305,6 +3305,26 @@ testthat::describe("srv_teal snapshot manager", { } ) }) + + testthat::it("is disabled by teal.snapshot_manager.enable = FALSE", { + withr::with_options(list(teal.snapshot_manager.enable = FALSE), { + testthat::expect_null(ui_snapshot_manager_panel("snapshot_manager_panel")) + shiny::testServer( + app = srv_teal, + args = list( + id = "test", + data = teal.data::teal_data(iris = iris), + modules = modules( + module("module_1", server = function(id, data) data) + ) + ), + expr = { + testthat::expect_null(snapshots) + testthat::expect_null(output[["snapshot_manager_panel-module-snapshot_list"]]) + } + ) + }) + }) }) testthat::describe("Datanames with special symbols", { diff --git a/tests/testthat/test-shinytest2-teal_data_module.R b/tests/testthat/test-shinytest2-teal_data_module.R index cc649bd895..5676ad022d 100644 --- a/tests/testthat/test-shinytest2-teal_data_module.R +++ b/tests/testthat/test-shinytest2-teal_data_module.R @@ -123,24 +123,6 @@ testthat::test_that("e2e: teal_data_module modal stays visible on startup when ` app$stop() }) -testthat::test_that("e2e: teal_data_module modal close button is enabled from disabled when data is ready", { - skip_if_too_deep(5) - app <- TealAppDriver$new( - init( - data = example_teal_data_module(needs_submit = TRUE, once = FALSE), - modules = example_module(label = "Example Module") - ) - ) - - testthat::expect_identical( - app$get_attr("#teal-close_teal_data_module_modal", "disabled"), - "disabled" - ) - app$click("teal-teal_data_module-submit") - testthat::expect_true(is.na(app$get_attr("#teal-close_teal_data_module_modal", "disabled"))) - app$stop() -}) - testthat::test_that("e2e: datasets from teal_data_module show in filter panel", { skip_if_too_deep(5) tdm <- teal_data_module( diff --git a/vignettes/teal-options.Rmd b/vignettes/teal-options.Rmd index fe6bda7961..4387a1397c 100644 --- a/vignettes/teal-options.Rmd +++ b/vignettes/teal-options.Rmd @@ -172,6 +172,12 @@ This option enables URL-based module navigation in `teal` apps. When set to `TRU Default: `FALSE`. +### `teal.snapshot_manager.enable` (`logical`) + +This option controls whether the Snapshot Manager panel (the camera icon in the tabset bar) is rendered in a `teal` app. When set to `FALSE`, both `ui_snapshot_manager_panel()` and `srv_snapshot_manager_panel()` return `NULL`, so the panel is not displayed and no snapshot logic is attached. + +Default: `TRUE`. + # Deprecated options ### `teal_logging` From 830613b0e928c7d326b6829d9ef3628e6472e20b Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 19 May 2026 10:51:20 +0200 Subject: [PATCH 33/33] fix tests --- tests/testthat/test-module_teal.R | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/testthat/test-module_teal.R b/tests/testthat/test-module_teal.R index 6ead8bd149..8c10ad949b 100644 --- a/tests/testthat/test-module_teal.R +++ b/tests/testthat/test-module_teal.R @@ -3318,10 +3318,7 @@ testthat::describe("srv_teal snapshot manager", { module("module_1", server = function(id, data) data) ) ), - expr = { - testthat::expect_null(snapshots) - testthat::expect_null(output[["snapshot_manager_panel-module-snapshot_list"]]) - } + expr = testthat::expect_null(snapshots) ) }) })