Compare commits

..

4 Commits

Author SHA1 Message Date
schloerke
f2be21861f devtools::document() (GitHub Actions) 2023-04-18 20:11:51 +00:00
Barret Schloerke
fce5216000 Add App State helper method to set options. Use it 2023-04-18 16:05:45 -04:00
Barret Schloerke
e418f3540a Use new getCurrentAppStateOptions() helper method paired with isRunning() 2023-04-18 16:05:45 -04:00
Barret Schloerke
977383de7f Move isRunning() to R/app-state.R to put all app state code in one location 2023-04-18 16:01:18 -04:00
34 changed files with 143 additions and 217 deletions

View File

@@ -111,7 +111,7 @@ Suggests:
ragg,
showtext,
sass
URL: https://shiny.posit.co/
URL: https://shiny.rstudio.com/
BugReports: https://github.com/rstudio/shiny/issues
Collate:
'globals.R'

18
NEWS.md
View File

@@ -12,24 +12,10 @@
* `Map` objects are now initialized at load time instead of build time. This avoids potential problems that could arise from storing `fastmap` objects into the built Shiny package. (#3775)
* Allow for `shiny:::toJSON()` to respect if `digits=` has class `"AsIs"` which represents if `use_signif=` is `TRUE` or `FALSE`. This is useful for testing to keep the digits smaller. For example, setting `options(shiny.json.digits = 4)` will save 4 digits after the decimal, rather than the default of `I(16)` which will save 16 significant digits. (#3819)
### Bug fixes
* Fixed #3771: Sometimes the error `ion.rangeSlider.min.js: i.stopPropagation is not a function` would appear in the JavaScript console. (#3772)
* Fixed #3833: When `width` is provided to `textAreaInput()`, we now correctly set the width of the `<textarea>` element. (#3838)
* Fixes #3840: `updateSliderInput()` now warns when attempting to set invalid `min`, `max`, or `value` values. Sending an invalid update message to an input no longer causes other update messages to fail. (#3843)
# shiny 1.7.4.1
## Full changelog
* Closed #3849: In R-devel, a warning was raised when Shiny was loaded because `as.numeric_version()` was called with a number instead of a string. (#3850)
# shiny 1.7.4
## Full changelog
@@ -179,7 +165,7 @@ This release focuses on improvements in three main areas:
1. Better theming (and Bootstrap 4) support:
* The `theme` argument of `fluidPage()`, `navbarPage()`, and `bootstrapPage()` all now understand `bslib::bs_theme()` objects, which can be used to opt-into Bootstrap 4, use any Bootswatch theme, and/or implement custom themes without writing any CSS.
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/theming.html#dynamic) or some other "real-time" theming tool like `bslib::bs_themer()`.
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/bslib.html#dynamic) or some other "real-time" theming tool like `bslib::bs_themer()`.
* For more details, see [`{bslib}`'s website](https://rstudio.github.io/bslib/)
2. Caching of `reactive()` and `render*()` (e.g. `renderText()`, `renderTable()`, etc) expressions.
@@ -572,7 +558,7 @@ This is a significant release for Shiny, with a major new feature that was nearl
* Fixed #1600: URL-encoded bookmarking did not work with sliders that had dates or date-times. (#1961)
* Fixed #1962: [File dragging and dropping](https://posit.co/blog/shiny-1-0-4/) broke in the presence of jQuery version 3.0 as introduced by the [rhandsontable](https://jrowen.github.io/rhandsontable/) [htmlwidget](https://www.htmlwidgets.org/). (#2005)
* Fixed #1962: [File dragging and dropping](https://www.rstudio.com/blog/shiny-1-0-4/) broke in the presence of jQuery version 3.0 as introduced by the [rhandsontable](https://jrowen.github.io/rhandsontable/) [htmlwidget](https://www.htmlwidgets.org/). (#2005)
* Improved the error handling inside the `addResourcePath()` function, to give end users more informative error messages when the `directoryPath` argument cannot be normalized. This is especially useful for `runtime: shiny_prerendered` Rmd documents, like `learnr` tutorials. (#1968)

View File

@@ -7,6 +7,17 @@ NULL
.globals$appState <- NULL
#' Check whether a Shiny application is running
#'
#' This function tests whether a Shiny application is currently running.
#'
#' @return `TRUE` if a Shiny application is currently running. Otherwise,
#' `FALSE`.
#' @export
isRunning <- function() {
!is.null(getCurrentAppState())
}
initCurrentAppState <- function(appobj) {
if (!is.null(.globals$appState)) {
stop("Can't initialize current app state when another is currently active.")
@@ -21,6 +32,14 @@ getCurrentAppState <- function() {
.globals$appState
}
getCurrentAppStateOptions <- function() {
.globals$appState$options
}
setCurrentAppStateOptions <- function(options) {
stopifnot(isRunning())
.globals$appState$options <- options
}
clearCurrentAppState <- function() {
.globals$appState <- NULL
}

View File

@@ -159,8 +159,8 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
#' ```
#'
#' To use different settings for a session-scoped cache, you can set
#' `session$cache` at the top of your server function. By default, it will
#' create a 200 MB memory cache for each session, but you can replace it with
#' `self$cache` at the top of your server function. By default, it will create
#' a 200 MB memory cache for each session, but you can replace it with
#' something different. To use the session-scoped cache, you must also call
#' `bindCache()` with `cache="session"`. This will create a 100 MB cache for
#' the session:

View File

@@ -374,7 +374,8 @@ collapseSizes <- function(padding) {
#' @param inverse `TRUE` to use a dark background and light text for the
#' navigation bar
#' @param collapsible `TRUE` to automatically collapse the navigation
#' elements into an expandable menu on mobile devices or narrow window widths.
#' elements into a menu when the width of the browser is less than 940 pixels
#' (useful for viewing on smaller touchscreen device)
#' @param fluid `TRUE` to use a fluid layout. `FALSE` to use a fixed
#' layout.
#' @param windowTitle the browser window title (as a character string). The

View File

@@ -16,6 +16,12 @@
s3_register("knitr::knit_print", "reactive")
s3_register("knitr::knit_print", "shiny.appobj")
s3_register("knitr::knit_print", "shiny.render.function")
# Shiny 1.4.0 bumps jQuery 1.x to 3.x, which caused a problem
# with static-rendering of htmlwidgets, and htmlwidgets 1.5
# includes a fix for this problem
# https://github.com/rstudio/shiny/issues/2630
register_upgrade_message("htmlwidgets", 1.5)
}

View File

@@ -4,7 +4,7 @@
#' from a list of values.
#'
#' By default, `selectInput()` and `selectizeInput()` use the JavaScript library
#' \pkg{selectize.js} (<https://selectize.dev/) instead of
#' \pkg{selectize.js} (<https://github.com/selectize/selectize.js>) instead of
#' the basic select input element. To use the standard HTML select input
#' element, use `selectInput()` with `selectize=FALSE`.
#'
@@ -172,7 +172,7 @@ needOptgroup <- function(choices) {
#' @rdname selectInput
#' @param ... Arguments passed to `selectInput()`.
#' @param options A list of options. See the documentation of \pkg{selectize.js}(<https://selectize.dev/docs/usage>)
#' @param options A list of options. See the documentation of \pkg{selectize.js}
#' for possible options (character option values inside [base::I()] will
#' be treated as literal JavaScript code; see [renderDataTable()]
#' for details).
@@ -287,7 +287,7 @@ selectizeStaticDependency <- function(version) {
#'
#' By default, `varSelectInput()` and `selectizeInput()` use the
#' JavaScript library \pkg{selectize.js}
#' (<https://selectize.dev/>) to instead of the basic
#' (<https://github.com/selectize/selectize.js>) to instead of the basic
#' select input element. To use the standard HTML select input element, use
#' `selectInput()` with `selectize=FALSE`.
#'
@@ -383,7 +383,7 @@ varSelectInput <- function(
#' @rdname varSelectInput
#' @param ... Arguments passed to `varSelectInput()`.
#' @param options A list of options. See the documentation of \pkg{selectize.js}(<https://selectize.dev/docs/usage>)
#' @param options A list of options. See the documentation of \pkg{selectize.js}
#' for possible options (character option values inside [base::I()] will
#' be treated as literal JavaScript code; see [renderDataTable()]
#' for details).

View File

@@ -52,7 +52,7 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
style <- css(
# The width is specified on the parent div.
width = if (!is.null(width)) "100%",
width = if (!is.null(width)) "width: 100%;",
height = validateCssUnit(height),
resize = resize
)

View File

@@ -274,7 +274,7 @@ MockShinySession <- R6Class(
self$token <- createUniqueId(16)
# Copy app-level options
self$options <- getCurrentAppState()$options
self$options <- getCurrentAppStateOptions()
self$cache <- cachem::cache_mem()
self$appcache <- cachem::cache_mem()

View File

@@ -2187,8 +2187,8 @@ maskReactiveContext <- function(expr) {
#' @param autoDestroy If `TRUE` (the default), the observer will be
#' automatically destroyed when its domain (if any) ends.
#' @param ignoreNULL Whether the action should be triggered (or value
#' calculated, in the case of `eventReactive`) when the input event expression
#' is `NULL`. See Details.
#' calculated, in the case of `eventReactive`) when the input is
#' `NULL`. See Details.
#' @param ignoreInit If `TRUE`, then, when this `observeEvent` is
#' first created/initialized, ignore the `handlerExpr` (the second
#' argument), whether it is otherwise supposed to run or not. The default is

View File

@@ -495,16 +495,6 @@ serviceApp <- function() {
.shinyServerMinVersion <- '0.3.4'
#' Check whether a Shiny application is running
#'
#' This function tests whether a Shiny application is currently running.
#'
#' @return `TRUE` if a Shiny application is currently running. Otherwise,
#' `FALSE`.
#' @export
isRunning <- function() {
!is.null(getCurrentAppState())
}
# Returns TRUE if we're running in Shiny Server or other hosting environment,

View File

@@ -19,10 +19,10 @@ getShinyOption <- function(name, default = NULL) {
}
# Check if there's a current app
app_state <- getCurrentAppState()
if (!is.null(app_state)) {
if (name %in% names(app_state$options)) {
return(app_state$options[[name]])
if (isRunning()) {
app_state_options <- getCurrentAppStateOptions()
if (name %in% names(app_state_options)) {
return(app_state_options[[name]])
} else {
return(default)
}
@@ -199,11 +199,12 @@ shinyOptions <- function(...) {
# If not in a session, but we have a currently running app, modify options
# at the app level.
app_state <- getCurrentAppState()
if (!is.null(app_state)) {
if (isRunning()) {
# Modify app-level options
app_state$options <- dropNulls(mergeVectors(app_state$options, newOpts))
return(invisible(app_state$options))
setCurrentAppStateOptions(
dropNulls(mergeVectors(getCurrentAppStateOptions(), newOpts))
)
return(invisible(getCurrentAppStateOptions()))
}
# If no currently running app, modify global options and return them.
@@ -218,9 +219,8 @@ shinyOptions <- function(...) {
return(session$options)
}
app_state <- getCurrentAppState()
if (!is.null(app_state)) {
return(app_state$options)
if (isRunning()) {
return(getCurrentAppStateOptions())
}
return(.globals$options)

View File

@@ -33,12 +33,8 @@ createUniqueId <- function(bytes, prefix = "", suffix = "") {
}
toJSON <- function(x, ..., dataframe = "columns", null = "null", na = "null",
auto_unbox = TRUE,
# Shiny has had a legacy value of 16 significant digits
# We can use `I(16)` mixed with the default behavior in jsonlite's `use_signif=`
# https://github.com/jeroen/jsonlite/commit/728efa9
digits = getOption("shiny.json.digits", I(16)), use_signif = is(digits, "AsIs"),
force = TRUE, POSIXt = "ISO8601", UTC = TRUE,
auto_unbox = TRUE, digits = getOption("shiny.json.digits", 16),
use_signif = TRUE, force = TRUE, POSIXt = "ISO8601", UTC = TRUE,
rownames = FALSE, keep_vec_names = TRUE, strict_atomic = TRUE) {
if (strict_atomic) {
@@ -742,7 +738,7 @@ ShinySession <- R6Class(
private$.outputOptions <- list()
# Copy app-level options
self$options <- getCurrentAppState()$options
self$options <- getCurrentAppStateOptions()
self$cache <- cachem::cache_mem(max_size = 200 * 1024^2)

View File

@@ -48,6 +48,32 @@ is_installed <- function(pkg, version = NULL) {
installed && isTRUE(get_package_version(pkg) >= version)
}
register_upgrade_message <- function(pkg, version, error = FALSE) {
msg <- sprintf(
"This version of '%s' is designed to work with '%s' >= %s.
Please upgrade via install.packages('%s').",
environmentName(environment(register_upgrade_message)),
pkg, version, pkg
)
cond <- if (error) stop else packageStartupMessage
if (pkg %in% loadedNamespaces() && !is_installed(pkg, version)) {
cond(msg)
}
# Always register hook in case pkg is loaded at some
# point the future (or, potentially, but less commonly,
# unloaded & reloaded)
setHook(
packageEvent(pkg, "onLoad"),
function(...) {
if (!is_installed(pkg, version)) cond(msg)
}
)
}
# Simplified version rlang:::s3_register() that just uses
# warning() instead of rlang::warn() when registration fails
# https://github.com/r-lib/rlang/blob/main/R/compat-s3-register.R
@@ -164,9 +190,11 @@ system_file <- function(..., package = "base") {
normalizePath(files, winslash = "/")
}
# A wrapper for `system.file()`, which caches the package path because
# `system.file()` can be slow. If a package is not installed, the result won't
# be cached.
# A wrapper for `system.file()`, which caches the results, because
# `system.file()` can be slow. Note that because of caching, if
# `system_file_cached()` is called on a package that isn't installed, then the
# package is installed, and then `system_file_cached()` is called again, it will
# still return "".
system_file_cached <- local({
pkg_dir_cache <- character()
@@ -178,9 +206,7 @@ system_file_cached <- local({
not_cached <- is.na(match(package, names(pkg_dir_cache)))
if (not_cached) {
pkg_dir <- system.file(package = package)
if (nzchar(pkg_dir)) {
pkg_dir_cache[[package]] <<- pkg_dir
}
pkg_dir_cache[[package]] <<- pkg_dir
} else {
pkg_dir <- pkg_dir_cache[[package]]
}

View File

@@ -37,43 +37,29 @@
#'
#'
#' # Testing a module --------------------------------------------------------
#' # Testing the server function doesn't require a UI, but we've included it
#' # here for completeness. In this simple app, a user clicks a button to
#' # multiply a value by the module's multiplier argument. In the tests below,
#' # we'll make sure the value is 1, 2, 4, etc. with each button click.
#' multModuleUI <- function(id) {
#' ns <- NS(id)
#' tagList(
#' textOutput(ns("txt")),
#' actionButton(ns("multiply_it"), "Multiply It")
#' )
#' }
#'
#' multModuleServer <- function(id, multiplier = 2) {
#' myModuleServer <- function(id, multiplier = 2, prefix = "I am ") {
#' moduleServer(id, function(input, output, session) {
#' the_value <- reactive({
#' max(input$multiply_it * multiplier, 1)
#' myreactive <- reactive({
#' input$x * multiplier
#' })
#' output$txt <- renderText({
#' paste("The value is", the_value())
#' paste0(prefix, myreactive())
#' })
#' })
#' }
#'
#' testServer(multModuleServer, args = list(multiplier = 2), {
#' # Set the initial button value to 0
#' session$setInputs(multiply_it = 0)
#' stopifnot(the_value() == 1)
#' stopifnot(output$txt == "The value is 1")
#'
#' # Simulate two button clicks
#' session$setInputs(multiply_it = 2)
#' stopifnot(the_value() == 4)
#' stopifnot(output$txt == "The value is 4")
#'
#' # Note: you're also free to use third-party
#' testServer(myModuleServer, args = list(multiplier = 2), {
#' session$setInputs(x = 1)
#' # You're also free to use third-party
#' # testing packages like testthat:
#' # expect_equal(myreactive(), 1)
#' # expect_equal(myreactive(), 2)
#' stopifnot(myreactive() == 2)
#' stopifnot(output$txt == "I am 2")
#'
#' session$setInputs(x = 2)
#' stopifnot(myreactive() == 4)
#' stopifnot(output$txt == "I am 4")
#' # Any additional arguments, below, are passed along to the module.
#' })
#' @export
testServer <- function(app = NULL, expr, args = list(), session = MockShinySession$new()) {

View File

@@ -423,23 +423,6 @@ updateSliderInput <- function(session = getDefaultReactiveDomain(), inputId, lab
{
validate_session_object(session)
if (!is.null(value)) {
if (!is.null(min) && !is.null(max)) {
# Validate value/min/max together if all three are provided
tryCatch(
validate_slider_value(min, max, value, "updateSliderInput"),
error = function(err) warning(conditionMessage(err), call. = FALSE)
)
} else if (length(value) < 1 || length(value) > 2 || any(is.na(value))) {
# Otherwise ensure basic assumptions about value are met
warning(
"In updateSliderInput(): value must be a single value or a length-2 ",
"vector and cannot contain NA values.",
call. = FALSE
)
}
}
# If no min/max/value is provided, we won't know the
# type, and this will return an empty string
dataType <- getSliderType(min, max, value)

View File

@@ -4,7 +4,7 @@ NULL
# @staticimports pkg:staticimports
# is_installed get_package_version system_file
# s3_register
# s3_register register_upgrade_message
# any_named any_unnamed
#' Make a random number generator repeatable

View File

@@ -16,7 +16,7 @@ Easily build rich and productive interactive web apps in R &mdash; no HTML/CSS/J
* A prebuilt set of highly sophisticated, customizable, and easy-to-use widgets (e.g., plots, tables, sliders, dropdowns, date pickers, and more).
* An attractive default look based on [Bootstrap](https://getbootstrap.com/) which can also be easily customized with the [bslib](https://github.com/rstudio/bslib) package or avoided entirely with more direct R bindings to HTML/CSS/JavaScript.
* Seamless integration with [R Markdown](https://shiny.rstudio.com/articles/interactive-docs.html), making it easy to embed numerous applications natively within a larger dynamic document.
* Tools for improving and monitoring performance, including native support for [async programming](https://posit.co/blog/shiny-1-1-0/), [caching](https://talks.cpsievert.me/20201117), [load testing](https://rstudio.github.io/shinyloadtest/), and more.
* Tools for improving and monitoring performance, including native support for [async programming](https://www.rstudio.com/blog/shiny-1-1-0/), [caching](https://talks.cpsievert.me/20201117), [load testing](https://rstudio.github.io/shinyloadtest/), and more.
* [Modules](https://shiny.rstudio.com/articles/modules.html): a framework for reducing code duplication and complexity.
* An ability to [bookmark application state](https://shiny.rstudio.com/articles/bookmarking-state.html) and/or [generate code to reproduce output(s)](https://github.com/rstudio/shinymeta).
* A rich ecosystem of extension packages for more [custom widgets](http://www.htmlwidgets.org/), [input validation](https://github.com/rstudio/shinyvalidate), [unit testing](https://github.com/rstudio/shinytest), and more.
@@ -45,10 +45,6 @@ For more examples and inspiration, check out the [Shiny User Gallery](https://sh
For help with learning fundamental Shiny programming concepts, check out the [Mastering Shiny](https://mastering-shiny.org/) book and the [Shiny Tutorial](https://shiny.rstudio.com/tutorial/). The former is currently more up-to-date with modern Shiny features, whereas the latter takes a deeper, more visual, dive into fundamental concepts.
## Join the conversation
If you want to chat about Shiny, meet other developers, or help us decide what to work on next, [join us on Discord](https://discord.gg/yMGCamUMnS).
## Getting Help
To ask a question about Shiny, please use the [RStudio Community website](https://community.rstudio.com/new-topic?category=shiny&tags=shiny).

View File

@@ -8241,8 +8241,7 @@
msg.to = data.value[1];
} else {
if (Array.isArray(data.value)) {
var errorReason = ["an empty array.", "a single-value array.", "an array with more than two values."];
throw "Slider requires two values to update with an array, but message value was " + errorReason[Math.min(data.value.length, 2)];
throw "Slider only contains a single value and cannot be updated with an array";
}
msg.from = data.value;
}
@@ -18338,17 +18337,8 @@
evt.message = message[i].message;
evt.binding = inputBinding;
(0, import_jquery38.default)(el).trigger(evt);
if (!evt.isDefaultPrevented()) {
try {
inputBinding.receiveMessage(el, evt.message);
} catch (error) {
console.error("[shiny] Error in inputBinding.receiveMessage()", {
error: error,
binding: inputBinding,
message: evt.message
});
}
}
if (!evt.isDefaultPrevented())
inputBinding.receiveMessage(el, evt.message);
}
}
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -166,8 +166,8 @@ instead of the default 200 MB:
}\if{html}{\out{</div>}}
To use different settings for a session-scoped cache, you can set
\code{session$cache} at the top of your server function. By default, it will
create a 200 MB memory cache for each session, but you can replace it with
\code{self$cache} at the top of your server function. By default, it will create
a 200 MB memory cache for each session, but you can replace it with
something different. To use the session-scoped cache, you must also call
\code{bindCache()} with \code{cache="session"}. This will create a 100 MB cache for
the session:

View File

@@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
% Please edit documentation in R/app-state.R
\name{isRunning}
\alias{isRunning}
\title{Check whether a Shiny application is running}

View File

@@ -58,7 +58,8 @@ tabPanels}
navigation bar}
\item{collapsible}{\code{TRUE} to automatically collapse the navigation
elements into an expandable menu on mobile devices or narrow window widths.}
elements into a menu when the width of the browser is less than 940 pixels
(useful for viewing on smaller touchscreen device)}
\item{fluid}{\code{TRUE} to use a fluid layout. \code{FALSE} to use a fixed
layout.}

View File

@@ -86,8 +86,8 @@ Positive, negative, and zero values are allowed.}
automatically destroyed when its domain (if any) ends.}
\item{ignoreNULL}{Whether the action should be triggered (or value
calculated, in the case of \code{eventReactive}) when the input event expression
is \code{NULL}. See Details.}
calculated, in the case of \code{eventReactive}) when the input is
\code{NULL}. See Details.}
\item{ignoreInit}{If \code{TRUE}, then, when this \code{observeEvent} is
first created/initialized, ignore the \code{handlerExpr} (the second

View File

@@ -48,7 +48,7 @@ but when \code{size} is set, it will be a box instead.}
\item{...}{Arguments passed to \code{selectInput()}.}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
for details).}
@@ -62,7 +62,7 @@ from a list of values.
}
\details{
By default, \code{selectInput()} and \code{selectizeInput()} use the JavaScript library
\pkg{selectize.js} (<https://selectize.dev/) instead of
\pkg{selectize.js} (\url{https://github.com/selectize/selectize.js}) instead of
the basic select input element. To use the standard HTML select input
element, use \code{selectInput()} with \code{selectize=FALSE}.

View File

@@ -47,42 +47,28 @@ testServer(server, {
# Testing a module --------------------------------------------------------
# Testing the server function doesn't require a UI, but we've included it
# here for completeness. In this simple app, a user clicks a button to
# multiply a value by the module's multiplier argument. In the tests below,
# we'll make sure the value is 1, 2, 4, etc. with each button click.
multModuleUI <- function(id) {
ns <- NS(id)
tagList(
textOutput(ns("txt")),
actionButton(ns("multiply_it"), "Multiply It")
)
}
multModuleServer <- function(id, multiplier = 2) {
myModuleServer <- function(id, multiplier = 2, prefix = "I am ") {
moduleServer(id, function(input, output, session) {
the_value <- reactive({
max(input$multiply_it * multiplier, 1)
myreactive <- reactive({
input$x * multiplier
})
output$txt <- renderText({
paste("The value is", the_value())
paste0(prefix, myreactive())
})
})
}
testServer(multModuleServer, args = list(multiplier = 2), {
# Set the initial button value to 0
session$setInputs(multiply_it = 0)
stopifnot(the_value() == 1)
stopifnot(output$txt == "The value is 1")
# Simulate two button clicks
session$setInputs(multiply_it = 2)
stopifnot(the_value() == 4)
stopifnot(output$txt == "The value is 4")
# Note: you're also free to use third-party
testServer(myModuleServer, args = list(multiplier = 2), {
session$setInputs(x = 1)
# You're also free to use third-party
# testing packages like testthat:
# expect_equal(myreactive(), 1)
# expect_equal(myreactive(), 2)
stopifnot(myreactive() == 2)
stopifnot(output$txt == "I am 2")
session$setInputs(x = 2)
stopifnot(myreactive() == 4)
stopifnot(output$txt == "I am 4")
# Any additional arguments, below, are passed along to the module.
})
}

View File

@@ -62,7 +62,7 @@ the example section for a small demo of this feature.}
\item{selected}{The initially selected value (or multiple values if \code{multiple = TRUE}). If not specified then defaults to the first value for
single-select lists and no values for multiple select lists.}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
for details).}

View File

@@ -42,7 +42,7 @@ but when \code{size} is set, it will be a box instead.}
\item{...}{Arguments passed to \code{varSelectInput()}.}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
for details).}
@@ -57,7 +57,7 @@ from the column names of a data frame.
\details{
By default, \code{varSelectInput()} and \code{selectizeInput()} use the
JavaScript library \pkg{selectize.js}
(\url{https://selectize.dev/}) to instead of the basic
(\url{https://github.com/selectize/selectize.js}) to instead of the basic
select input element. To use the standard HTML select input element, use
\code{selectInput()} with \code{selectize=FALSE}.
}

View File

@@ -197,16 +197,7 @@ class SliderInputBinding extends TextInputBindingBase {
msg.to = data.value[1];
} else {
if (Array.isArray(data.value)) {
const errorReason = [
"an empty array.",
"a single-value array.",
"an array with more than two values.",
];
throw (
"Slider requires two values to update with an array, " +
"but message value was " +
errorReason[Math.min(data.value.length, 2)]
);
throw "Slider only contains a single value and cannot be updated with an array";
}
msg.from = data.value;
}

View File

@@ -725,16 +725,8 @@ class ShinyApp {
evt.message = message[i].message;
evt.binding = inputBinding;
$(el).trigger(evt);
if (!evt.isDefaultPrevented()) {
try {
inputBinding.receiveMessage(el, evt.message);
} catch (error) {
console.error(
"[shiny] Error in inputBinding.receiveMessage()",
{ error, binding: inputBinding, message: evt.message }
);
}
}
if (!evt.isDefaultPrevented())
inputBinding.receiveMessage(el, evt.message);
}
}
}

1
tests/testthat/apps Submodule

Submodule tests/testthat/apps added at c471e6449e

View File

@@ -268,27 +268,3 @@ test_that("quoToFunction handles nested quosures", {
func <- quoToFunction(quo_outer, "foo")
expect_identical(func(), 2)
})
test_that("toJSON can set digits using options - default", {
withr::local_options(list(shiny.json.digits = NULL))
expect_equal(
as.character(toJSON(pi)),
"[3.141592653589793]"
)
})
test_that("toJSON can set digits using options - number", {
withr::local_options(list(shiny.json.digits = 4))
expect_equal(
as.character(toJSON(pi)),
"[3.1416]"
)
})
test_that("toJSON can set digits using options - asis number", {
withr::local_options(list(shiny.json.digits = I(4)))
expect_equal(
as.character(toJSON(pi)),
"[3.142]"
)
})