Compare commits

..

7 Commits

Author SHA1 Message Date
Alan Dipert
a036aa4607 still broken, progress on new modules and proxied mocksession 2020-03-07 00:10:07 +00:00
Alan Dipert
2c2ca4b58e simplifications 2020-03-06 23:40:27 +00:00
Alan Dipert
c2c0a0d836 A little churn 2020-03-06 00:35:35 +00:00
Alan Dipert
ed93d42a6e Simplify differentiation strategy 2020-03-05 21:24:38 +00:00
Alan Dipert
6fa332aa77 Add changes and a failing test 2020-03-05 19:46:35 +00:00
Alan Dipert
14b572e115 Passing existing tests 2020-03-05 19:25:33 +00:00
Alan Dipert
946435f25d Add class to ShinyMockSession and fix tests 2020-03-05 18:11:42 +00:00
124 changed files with 4702 additions and 6580 deletions

View File

@@ -6,11 +6,7 @@ matrix:
r_packages:
- devtools
- rprojroot
script: ./tools/documentation/checkDocsCurrent.sh
env:
# GITHUB_PAT for gh::gh calls
- secure: "Hk4piVNtDobLT1dQPnCOcM7sOlwNGJOU5cpvbRvOxYSgxP+Bj2MyRZMe825rdHkHbFez0h8w3tJOBf9DDBH7PC1BhhNll2+WM/WxGlkNleg8vsoH/Xopffl+2YgtWbAYZjQ2j0QYdgNn0e/TY86/ggk9qit6+gpsZ7z/HmWQuVY="
script: ./tools/checkDocsCurrent.sh
- name: "Javascript check"
language: node_js
cache: yarn

View File

@@ -79,9 +79,7 @@ Imports:
crayon,
rlang (>= 0.4.0),
fastmap (>= 1.0.0),
withr,
commonmark (>= 1.7),
glue (>= 1.3.2)
withr
Suggests:
datasets,
Cairo (>= 1.5-5),
@@ -97,13 +95,11 @@ Suggests:
future,
dygraphs
Remotes:
rstudio/htmltools,
rstudio/shinytest
rstudio/htmltools
URL: http://shiny.rstudio.com
BugReports: https://github.com/rstudio/shiny/issues
Collate:
'app.R'
'app_template.R'
'bookmark-state-local.R'
'stack.R'
'bookmark-state.R'
@@ -159,7 +155,6 @@ Collate:
'priorityqueue.R'
'progress.R'
'react.R'
'reexports.R'
'render-cached-plot.R'
'render-plot.R'
'render-table.R'
@@ -175,9 +170,9 @@ Collate:
'snapshot.R'
'tar.R'
'test-export.R'
'test-server.R'
'test-module.R'
'test.R'
'update-input.R'
RoxygenNote: 7.1.0
RoxygenNote: 7.0.2
Encoding: UTF-8
Roxygen: list(markdown = TRUE)

View File

@@ -142,10 +142,8 @@ export(loadSupport)
export(mainPanel)
export(makeReactiveBinding)
export(markRenderFunction)
export(markdown)
export(maskReactiveContext)
export(memoryCache)
export(migrateLegacyShinytest)
export(modalButton)
export(modalDialog)
export(moduleServer)
@@ -231,7 +229,6 @@ export(setSerializer)
export(shinyApp)
export(shinyAppDir)
export(shinyAppFile)
export(shinyAppTemplate)
export(shinyOptions)
export(shinyServer)
export(shinyUI)
@@ -255,7 +252,6 @@ export(strong)
export(submitButton)
export(suppressDependencies)
export(tabPanel)
export(tabPanelBody)
export(tableOutput)
export(tabsetPanel)
export(tag)
@@ -267,6 +263,7 @@ export(tagHasAttribute)
export(tagList)
export(tagSetChildren)
export(tags)
export(testModule)
export(testServer)
export(textAreaInput)
export(textInput)
@@ -275,7 +272,6 @@ export(throttle)
export(titlePanel)
export(uiOutput)
export(updateActionButton)
export(updateActionLink)
export(updateCheckboxGroupInput)
export(updateCheckboxInput)
export(updateDateInput)
@@ -318,43 +314,6 @@ importFrom(fastmap,is.key_missing)
importFrom(fastmap,key_missing)
importFrom(grDevices,dev.cur)
importFrom(grDevices,dev.set)
importFrom(htmltools,HTML)
importFrom(htmltools,a)
importFrom(htmltools,br)
importFrom(htmltools,code)
importFrom(htmltools,div)
importFrom(htmltools,em)
importFrom(htmltools,h1)
importFrom(htmltools,h2)
importFrom(htmltools,h3)
importFrom(htmltools,h4)
importFrom(htmltools,h5)
importFrom(htmltools,h6)
importFrom(htmltools,hr)
importFrom(htmltools,htmlTemplate)
importFrom(htmltools,img)
importFrom(htmltools,includeCSS)
importFrom(htmltools,includeHTML)
importFrom(htmltools,includeMarkdown)
importFrom(htmltools,includeScript)
importFrom(htmltools,includeText)
importFrom(htmltools,is.singleton)
importFrom(htmltools,p)
importFrom(htmltools,pre)
importFrom(htmltools,singleton)
importFrom(htmltools,span)
importFrom(htmltools,strong)
importFrom(htmltools,suppressDependencies)
importFrom(htmltools,tag)
importFrom(htmltools,tagAppendAttributes)
importFrom(htmltools,tagAppendChild)
importFrom(htmltools,tagAppendChildren)
importFrom(htmltools,tagGetAttribute)
importFrom(htmltools,tagHasAttribute)
importFrom(htmltools,tagList)
importFrom(htmltools,tagSetChildren)
importFrom(htmltools,tags)
importFrom(htmltools,validateCssUnit)
importFrom(htmltools,withTags)
importFrom(promises,"%...!%")
importFrom(promises,"%...>%")
importFrom(withr,with_options)

26
NEWS.md
View File

@@ -7,16 +7,8 @@ shiny 1.4.0.9001
### New features
* The new `shinyAppTemplate()` function creates a new template Shiny application, where components are optional, such as helper files in an R/ subdirectory, a module, and various kinds of tests. ([#2704](https://github.com/rstudio/shiny/pull/2704))
* `runTests()` is a new function that behaves much like R CMD check. `runTests()` invokes all of the top-level R files in the tests/ directory inside an application, in that application's environment. ([#2585](https://github.com/rstudio/shiny/pull/2585))
* `testServer()` and `testModule()` are two new functions for testing reactive behavior inside server functions and modules, respectively. ([#2682](https://github.com/rstudio/shiny/pull/2682), [#2764](https://github.com/rstudio/shiny/pull/2764))
* The new `moduleServer` function provides a simpler interface for creating and using modules. ([#2773](https://github.com/rstudio/shiny/pull/2773))
* Resolved [#2732](https://github.com/rstudio/shiny/issues/2732): `markdown()` is a new function for writing Markdown with Github extensions directly in Shiny UIs. Markdown rendering is performed by the [commonmark](https://github.com/jeroen/commonmark) package. ([#2737](https://github.com/rstudio/shiny/pull/2737))
### Minor new features and improvements
* Fixed [#2042](https://github.com/rstudio/shiny/issues/2042), [#2628](https://github.com/rstudio/shiny/issues/2628): In a `dateInput` and `dateRangeInput`, disabled months and years are now a lighter gray, to make it easier to see that they are disabled. ([#2690](https://github.com/rstudio/shiny/pull/2690))
@@ -27,12 +19,6 @@ shiny 1.4.0.9001
* `getDefaultReactiveDomain()` can now be called inside a `session$onSessionEnded` callback and will return the calling `session` information. ([#2757](https://github.com/rstudio/shiny/pull/2757))
* Added a `'function'` class to `reactive()` and `reactiveVal()` objects. ([#2793](https://github.com/rstudio/shiny/pull/2793))
* Added a new option (`type = "hidden"`) to `tabsetPanel()`, making it easier to set the active tab via other input controls (e.g., `radioButtons()`) rather than tabs or pills. Use this option in conjunction with `updateTabsetPanel()` and the new `tabsetPanelBody()` function (see `help(tabsetPanel)` for an example and more details). ([#2814](https://github.com/rstudio/shiny/pull/2814))
* Added function `updateActionLink()` to update an `actionLink()` label and/or icon value. ([#2811](https://github.com/rstudio/shiny/pull/2811))
### Bug fixes
* Fixed [#2606](https://github.com/rstudio/shiny/issues/2606): `debounce()` would not work properly if the code in the reactive expression threw an error on the first run. ([#2652](https://github.com/rstudio/shiny/pull/2652))
@@ -42,18 +28,6 @@ shiny 1.4.0.9001
### Documentation Updates
shiny 1.4.0.2
===========
Minor patch release: fixed some timing-dependent tests failed intermittently on CRAN build machines.
shiny 1.4.0.1
===========
Minor patch release to account for changes to the grid package that will be upcoming in the R 4.0 release ([#2776](https://github.com/rstudio/shiny/pull/2776)).
shiny 1.4.0
===========

65
R/app.R
View File

@@ -227,6 +227,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
onStart <- function() {
oldwd <<- getwd()
setwd(appDir)
monitorHandle <<- initAutoReloadMonitor(appDir)
# TODO: we should support hot reloading on global.R and R/*.R changes.
if (getOption("shiny.autoload.r", TRUE)) {
loadSupport(appDir, renv=sharedEnv, globalrenv=globalenv())
@@ -234,17 +235,11 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
if (file.exists(file.path.ci(appDir, "global.R")))
sourceUTF8(file.path.ci(appDir, "global.R"))
}
monitorHandle <<- initAutoReloadMonitor(appDir)
}
onStop <- function() {
setwd(oldwd)
# It is possible that while calling appObj()$onStart() or loadingSupport, an error occured
# This will cause `onStop` to be called.
# The `oldwd` will exist, but `monitorHandle` is not a function yet.
if (is.function(monitorHandle)) {
monitorHandle()
monitorHandle <<- NULL
}
monitorHandle()
monitorHandle <<- NULL
}
structure(
@@ -302,14 +297,14 @@ initAutoReloadMonitor <- function(dir) {
} else if (!identical(lastValue, times)) {
# We've changed!
lastValue <<- times
autoReloadCallbacks$invoke()
for (session in appsByToken$values()) {
session$reload()
}
}
invalidateLater(getOption("shiny.autoreload.interval", 500))
})
onStop(obs$destroy)
obs$destroy
}
@@ -330,49 +325,37 @@ initAutoReloadMonitor <- function(dir) {
#' @details The files are sourced in alphabetical order (as determined by
#' [list.files]). `global.R` is evaluated before the supporting R files in the
#' `R/` directory.
#' @param appDir The application directory. If `appDir` is `NULL` or
#' not supplied, the nearest enclosing directory that is a Shiny app, starting
#' with the current directory, is used.
#' @param appDir The application directory
#' @param renv The environmeny in which the files in the `R/` directory should
#' be evaluated.
#' @param globalrenv The environment in which `global.R` should be evaluated. If
#' `NULL`, `global.R` will not be evaluated at all.
#' @export
loadSupport <- function(appDir=NULL, renv=new.env(parent=globalenv()), globalrenv=globalenv()){
require(shiny)
if (is.null(appDir)) {
appDir <- findEnclosingApp(".")
}
loadSupport <- function(appDir, renv=new.env(parent=globalenv()), globalrenv=globalenv()){
if (!is.null(globalrenv)){
# Evaluate global.R, if it exists.
globalPath <- file.path.ci(appDir, "global.R")
if (file.exists(globalPath)){
withr::with_dir(appDir, {
sourceUTF8(basename(globalPath), envir=globalrenv)
})
if (file.exists(file.path.ci(appDir, "global.R"))){
sourceUTF8(file.path.ci(appDir, "global.R"), envir=globalrenv)
}
}
helpersDir <- file.path(appDir, "R")
disabled <- list.files(helpersDir, pattern="^_disable_autoload\\.r$", recursive=FALSE, ignore.case=TRUE)
if (length(disabled) > 0){
message("R/_disable_autoload.R detected; not loading the R/ directory automatically")
return(invisible(renv))
}
helpers <- list.files(helpersDir, pattern="\\.[rR]$", recursive=FALSE, full.names=TRUE)
# Ensure files in R/ are sorted according to the 'C' locale before sourcing.
# This convention is based on the default for packages. For details, see:
# https://cran.r-project.org/doc/manuals/r-release/R-exts.html#The-DESCRIPTION-file
helpers <- sort(helpers, method = "radix")
helpers <- normalizePath(helpers)
withr::with_dir(appDir, {
lapply(helpers, sourceUTF8, envir=renv)
})
if (length(helpers) > 0){
message("Automatically loading ", length(helpers), " .R file",
ifelse(length(helpers) != 1, "s", ""),
" found in the R/ directory.\nSee https://rstd.io/shiny-autoload for more info.")
}
lapply(helpers, sourceUTF8, envir=renv)
invisible(renv)
}
@@ -446,19 +429,13 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
if (getOption("shiny.autoload.r", TRUE)) {
loadSupport(appDir, renv=sharedEnv, globalrenv=NULL)
}
if (!is.null(appObj()$onStart)) appObj()$onStart()
monitorHandle <<- initAutoReloadMonitor(appDir)
invisible()
if (!is.null(appObj()$onStart)) appObj()$onStart()
}
onStop <- function() {
setwd(oldwd)
# It is possible that while calling appObj()$onStart() or loadingSupport, an error occured
# This will cause `onStop` to be called.
# The `oldwd` will exist, but `monitorHandle` is not a function yet.
if (is.function(monitorHandle)) {
monitorHandle()
monitorHandle <<- NULL
}
monitorHandle()
monitorHandle <<- NULL
}
structure(

View File

@@ -1,283 +0,0 @@
#' Generate a Shiny application from a template
#'
#' This function populates a directory with files for a Shiny application.
#'
#' In an interactive R session, this function will, by default, prompt the user
#' which components to add to the application.
#'
#' The full example application includes the following files and directories:
#'
#' ```
#' appdir/
#' |- app.R
#' |- R
#' | |- my-module.R
#' | `- sort.R
#' `- tests
#' |- shinytest.R
#' |- shinytest
#' | `- mytest.R
#' |- testthat.R
#' `- testthat
#' |- helper-load.R
#' |- test-mymodule.R
#' |- test-server.R
#' `- test-sort.R
#' ```
#'
#' Some notes about these files:
#' * `app.R` is the main application file.
#' * All files in the `R/` subdirectory are automatically sourced when the
#' application is run.
#' * `R/sort.R` and `R/my-module.R` are automatically sourced when
#' the application is run. The first contains a function `lexical_sort()`,
#' and the second contains code for a [Shiny module](moduleServer()) which
#' is used in the application.
#' * `tests/` contains various tests for the application. You may
#' choose to use or remove any of them. They can be executed by the
#' [runTests()] function.
#' * `tests/shinytest.R` is a test runner for test files in the
#' `tests/shinytest/` directory.
#' * `tests/shinytest/mytest.R` is a test that uses the
#' [shinytest](https://rstudio.github.io/shinytest/) package to do
#' snapshot-based testing.
#' * `tests/testthat.R` is a test runner for test files in the
#' `tests/testthat/` directory using the [testthat](https://testthat.r-lib.org/) package.
#' * `tests/testthat/test-mymodule.R` is a test for an application's module server function.
#' * `tests/testthat/test-server.R` is a test for the application's server code
#' * `tests/testthat/test-sort.R` is a test for a supporting function in the `R/` directory.
#'
#' @param path Path to create new shiny application template.
#' @param examples Either one of "default", "ask", "all", or any combination of
#' "app", "rdir", "module", "shinytest", and "testthat". In an
#' interactive session, "default" falls back to "ask"; in a non-interactive
#' session, "default" falls back to "all". With "ask", this function will
#' prompt the user to select which template items will be added to the new app
#' directory. With "all", all template items will be added to the app
#' directory.
#' @param dryrun If `TRUE`, don't actually write any files; just print out which
#' files would be written.
#'
#' @export
shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
{
if (is.null(path)) {
stop("Please provide a `path`.")
}
# =======================================================
# Option handling
# =======================================================
choices <- c(
app = "app.R : Main application file",
rdir = "R/sort.R : Helper file with R code",
module = "R/my-module.R : Example module",
shinytest = "tests/shinytest/ : Tests using shinytest package",
testthat = "tests/testthat/ : Tests using testthat"
)
if (identical(examples, "default")) {
if (interactive()) {
examples <- "ask"
} else {
examples <- "all"
}
}
if (!identical(examples, "ask") &&
!identical(examples, "all") &&
any(! examples %in% names(choices)))
{
stop('`examples` must be one of "default", "ask", "all", or any combination of "',
paste(names(choices), collapse = '", "'), '".')
}
if (identical(examples, "ask")) {
response <- select_menu(
c(all = "All", choices),
title = paste0(
"Select which of the following to add at ", path, "/ :"
),
msg = "Enter one or more numbers (with spaces), or an empty line to exit: \n"
)
examples <- names(response)
}
examples <- unique(examples)
if ("all" %in% examples) {
examples <- names(choices)
}
if (length(examples) == 0) {
return(invisible())
}
if ("shinytest" %in% examples) {
if (system.file(package = "shinytest") != "" &&
utils::packageVersion("shinytest") <= "1.3.1.9000")
{
message(
"The tests/shinytest directory needs shinytest 1.4.0 or later to work properly.\n",
)
if (system.file(package = "shinytest") != "") {
message("You currently have shinytest ",
utils::packageVersion("shinytest"), " installed.")
}
}
}
# =======================================================
# Utility functions
# =======================================================
# Check if a directory is empty, ignoring certain files
dir_is_empty <- function(path) {
files <- list.files(path, all.files = TRUE, no.. = TRUE)
# Ignore .DS_Store files, which are sometimes automatically created on macOS
files <- setdiff(files, ".DS_Store")
return(length(files) != 0)
}
# Helper to resolve paths relative to our template
template_path <- function(...) {
system.file("app_template", ..., package = "shiny")
}
# Resolve path relative to destination
dest_path <- function(...) {
file.path(path, ...)
}
mkdir <- function(path) {
if (!dirExists(path)) {
message("Creating ", ensure_trailing_slash(path))
if (!dryrun) {
dir.create(path, recursive = TRUE)
}
}
}
# Copy a file from the template directory to the destination directory. If the
# file has templating code (it contains `{{` in the text), then run it through
# the htmlTemplate().
copy_file_one <- function(name) {
from <- template_path(name)
to <- dest_path(name)
message("Creating ", to)
if (file.exists(to)) {
stop(to, " already exists. Please remove it and try again.", call. = FALSE)
}
if (!dryrun) {
is_template <- any(grepl("{{", readLines(from), fixed = TRUE))
if (is_template) {
writeChar(
as.character(htmlTemplate(
from,
rdir = "rdir" %in% examples,
module = "module" %in% examples
)),
con = to,
eos = NULL
)
} else {
file.copy(from, to)
}
}
}
# Copy multiple files from template to destination.
copy_file <- function(names) {
for (name in names) {
copy_file_one(name)
}
}
# Copy the files for a tests/ subdirectory
copy_test_dir <- function(name) {
files <- dir(template_path("tests"), recursive = TRUE)
# Note: This is not the same as using dir(pattern = "^shinytest"), since
# that will not match files inside of shinytest/.
files <- files[grepl(paste0("^", name), files)]
# Filter out files that are not module files in the R directory.
if (! "rdir" %in% examples) {
# find all files in the testthat folder that are not module or server files
is_r_folder_file <- (!grepl("module|server", basename(files))) & (dirname(files) == "testthat")
files <- files[!is_r_folder_file]
}
# Filter out module files, if applicable.
if (! "module" %in% examples) {
files <- files[!grepl("module", files)]
}
mkdir(dest_path("tests"))
# Create any subdirectories if needed
dirs <- setdiff(unique(dirname(files)), ".")
for (dir in dirs) {
mkdir(dest_path("tests", dir))
}
copy_file(file.path("tests", files))
}
# =======================================================
# Main function
# =======================================================
if (is.null(path)) {
stop("`path` is missing.")
}
if (file.exists(path) && !dirExists(path)) {
stop(path, " exists but is not a directory.")
}
if (dirExists(path) && dir_is_empty(path)) {
if (interactive()) {
response <- readline(paste0(
ensure_trailing_slash(path),
" is not empty. Do you want to create a Shiny app in this directory anyway? [y/n] "
))
if (tolower(response) != "y") {
return(invisible())
}
}
} else {
mkdir(path)
}
if ("app" %in% examples) {
copy_file("app.R")
}
# R/ dir with non-module files
if ("rdir" %in% examples) {
non_module_files <- dir(template_path("R"), pattern = "[^(module)].R$")
mkdir(dest_path("R"))
copy_file(file.path("R", non_module_files))
}
# R/ dir with module files
if ("module" %in% examples) {
module_files <- dir(template_path("R"), pattern = "module.R$")
mkdir(dest_path("R"))
copy_file(file.path("R", module_files))
}
# tests/ dir
if ("shinytest" %in% examples) {
copy_test_dir("shinytest")
}
if ("testthat" %in% examples) {
copy_test_dir("testthat")
}
invisible()
}

View File

@@ -464,6 +464,8 @@ helpText <- function(...) {
#' Create a tab panel
#'
#' Create a tab panel that can be included within a [tabsetPanel()] or
#' a [navbarPage()].
#'
#' @param title Display title for tab
#' @param ... UI elements to include within the tab
@@ -487,21 +489,12 @@ helpText <- function(...) {
#' )
#' )
#' @export
#' @describeIn tabPanel Create a tab panel that can be included within a [tabsetPanel()] or a [navbarPage()].
tabPanel <- function(title, ..., value = title, icon = NULL) {
div(
class = "tab-pane",
title = title,
`data-value` = value,
`data-icon-class` = iconClass(icon),
...
)
}
#' @export
#' @describeIn tabPanel Create a tab panel that drops the title argument.
#' This function should be used within `tabsetPanel(type = "hidden")`. See [tabsetPanel()] for example usage.
tabPanelBody <- function(..., value = NULL, icon = NULL) {
tabPanel(title = NULL, ..., value = value, icon = icon)
divTag <- div(class="tab-pane",
title=title,
`data-value`=value,
`data-icon-class` = iconClass(icon),
...)
}
#' Create a tabset panel
@@ -517,13 +510,8 @@ tabPanelBody <- function(..., value = NULL, icon = NULL) {
#' @param selected The `value` (or, if none was supplied, the `title`)
#' of the tab that should be selected by default. If `NULL`, the first
#' tab will be selected.
#' @param type \describe{
#' \item{`"tabs"`}{Standard tab look}
#' \item{`"pills"`}{Selected tabs use the background fill color}
#' \item{`"hidden"`}{Hides the selectable tabs. Use `type = "hidden"` in
#' conjunction with [tabPanelBody()] and [updateTabsetPanel()] to control the
#' active tab via other input controls. (See example below)}
#' }
#' @param type Use "tabs" for the standard look; Use "pills" for a more plain
#' look where tabs are selected using a background fill color.
#' @param position This argument is deprecated; it has been discontinued in
#' Bootstrap 3.
#' @return A tabset that can be passed to [mainPanel()]
@@ -541,40 +529,11 @@ tabPanelBody <- function(..., value = NULL, icon = NULL) {
#' tabPanel("Table", tableOutput("table"))
#' )
#' )
#'
#' ui <- fluidPage(
#' sidebarLayout(
#' sidebarPanel(
#' radioButtons("controller", "Controller", 1:3, 1)
#' ),
#' mainPanel(
#' tabsetPanel(
#' id = "hidden_tabs",
#' # Hide the tab values.
#' # Can only switch tabs by using `updateTabsetPanel()`
#' type = "hidden",
#' tabPanelBody(value = "panel1", "Panel 1 content"),
#' tabPanelBody(value = "panel2", "Panel 2 content"),
#' tabPanelBody(value = "panel3", "Panel 3 content")
#' )
#' )
#' )
#' )
#'
#' server <- function(input, output, session) {
#' observeEvent(input$controller, {
#' updateTabsetPanel(session, "hidden_tabs", selected = paste0("panel", input$controller))
#' })
#' }
#'
#' if (interactive()) {
#' shinyApp(ui, server)
#' }
#' @export
tabsetPanel <- function(...,
id = NULL,
selected = NULL,
type = c("tabs", "pills", "hidden"),
type = c("tabs", "pills"),
position = NULL) {
if (!is.null(position)) {
shinyDeprecated(msg = paste("tabsetPanel: argument 'position' is deprecated;",
@@ -883,9 +842,42 @@ verbatimTextOutput <- function(outputId, placeholder = FALSE) {
#' @rdname plotOutput
#' @export
imageOutput <- function(outputId, width = "100%", height="400px",
click = NULL, dblclick = NULL, hover = NULL, brush = NULL,
click = NULL, dblclick = NULL,
hover = NULL, hoverDelay = NULL, hoverDelayType = NULL,
brush = NULL,
clickId = NULL, hoverId = NULL,
inline = FALSE) {
if (!is.null(clickId)) {
shinyDeprecated(
msg = paste("The 'clickId' argument is deprecated. ",
"Please use 'click' instead. ",
"See ?imageOutput or ?plotOutput for more information."),
version = "0.11.1"
)
click <- clickId
}
if (!is.null(hoverId)) {
shinyDeprecated(
msg = paste("The 'hoverId' argument is deprecated. ",
"Please use 'hover' instead. ",
"See ?imageOutput or ?plotOutput for more information."),
version = "0.11.1"
)
hover <- hoverId
}
if (!is.null(hoverDelay) || !is.null(hoverDelayType)) {
shinyDeprecated(
msg = paste("The 'hoverDelay'and 'hoverDelayType' arguments are deprecated. ",
"Please use 'hoverOpts' instead. ",
"See ?imageOutput or ?plotOutput for more information."),
version = "0.11.1"
)
hover <- hoverOpts(id = hover, delay = hoverDelay, delayType = hoverDelayType)
}
style <- if (!inline) {
paste("width:", validateCssUnit(width), ";", "height:", validateCssUnit(height))
}
@@ -992,6 +984,14 @@ imageOutput <- function(outputId, width = "100%", height="400px",
#' named list with `x` and `y` elements indicating the mouse
#' position. To control the hover time or hover delay type, you must use
#' [hoverOpts()].
#' @param clickId Deprecated; use `click` instead. Also see the
#' [clickOpts()] function.
#' @param hoverId Deprecated; use `hover` instead. Also see the
#' [hoverOpts()] function.
#' @param hoverDelay Deprecated; use `hover` instead. Also see the
#' [hoverOpts()] function.
#' @param hoverDelayType Deprecated; use `hover` instead. Also see the
#' [hoverOpts()] function.
#' @param brush Similar to the `click` argument, this can be `NULL`
#' (the default), a string, or an object created by the
#' [brushOpts()] function. If you use a value like
@@ -1175,12 +1175,16 @@ imageOutput <- function(outputId, width = "100%", height="400px",
#' }
#' @export
plotOutput <- function(outputId, width = "100%", height="400px",
click = NULL, dblclick = NULL, hover = NULL, brush = NULL,
click = NULL, dblclick = NULL,
hover = NULL, hoverDelay = NULL, hoverDelayType = NULL,
brush = NULL,
clickId = NULL, hoverId = NULL,
inline = FALSE) {
# Result is the same as imageOutput, except for HTML class
res <- imageOutput(outputId, width, height, click, dblclick,
hover, brush, inline)
hover, hoverDelay, hoverDelayType, brush,
clickId, hoverId, inline)
res$attribs$class <- "shiny-plot-output"
res

View File

@@ -1,3 +1,11 @@
#' @importFrom fastmap key_missing
#' @export
fastmap::key_missing
#' @importFrom fastmap is.key_missing
#' @export
fastmap::is.key_missing
validate_key <- function(key) {
if (!is.character(key) || length(key) != 1 || nchar(key) == 0) {
@@ -7,3 +15,4 @@ validate_key <- function(key) {
stop("Invalid key: ", key, ". Only lowercase letters and numbers are allowed.")
}
}

View File

@@ -10,7 +10,7 @@
#' then the server will receive click events even when the mouse is outside
#' the plotting area, as long as it is still inside the image.
#' @export
clickOpts <- function(id, clip = TRUE) {
clickOpts <- function(id = NULL, clip = TRUE) {
if (is.null(id))
stop("id must not be NULL")
@@ -36,7 +36,7 @@ clickOpts <- function(id, clip = TRUE) {
#' @param delay Maximum delay (in ms) between a pair clicks for them to be
#' counted as a double-click.
#' @export
dblclickOpts <- function(id, clip = TRUE, delay = 400) {
dblclickOpts <- function(id = NULL, clip = TRUE, delay = 400) {
if (is.null(id))
stop("id must not be NULL")
@@ -69,7 +69,7 @@ dblclickOpts <- function(id, clip = TRUE, delay = 400) {
#' `NULL` when the mouse exits the plotting area. If `FALSE`, the
#' value will stop changing when the cursor exits the plotting area.
#' @export
hoverOpts <- function(id, delay = 300,
hoverOpts <- function(id = NULL, delay = 300,
delayType = c("debounce", "throttle"), clip = TRUE,
nullOutside = TRUE) {
if (is.null(id))
@@ -117,7 +117,7 @@ hoverOpts <- function(id, delay = 300,
#' brush. Using `TRUE` is useful if you want to clear the brush whenever
#' the plot is updated.
#' @export
brushOpts <- function(id, fill = "#9cf", stroke = "#036",
brushOpts <- function(id = NULL, fill = "#9cf", stroke = "#036",
opacity = 0.25, delay = 300,
delayType = c("debounce", "throttle"), clip = TRUE,
direction = c("xy", "x", "y"),

View File

@@ -1,76 +1,59 @@
#' Find rows of data selected on an interactive plot.
#' Find rows of data that are selected by a brush
#'
#' @description
#' `brushedPoints()` returns rows from a data frame which are under a brush.
#' `nearPoints()` returns rows from a data frame which are near a click, hover,
#' or double-click. Alternatively, set `allRows = TRUE` to return all rows from
#' the input data with an additional column `selected_` that indicates which
#' rows of the would be selected.
#' This function returns rows from a data frame which are under a brush used
#' with [plotOutput()].
#'
#' @section ggplot2:
#' For plots created with ggplot2, it is not necessary to specify the
#' column names to `xvar`, `yvar`, `panelvar1`, and `panelvar2` as that
#' information can be automatically derived from the plot specification.
#' It is also possible for this function to return all rows from the input data
#' frame, but with an additional column `selected_`, which indicates which
#' rows of the input data frame are selected by the brush (`TRUE` for
#' selected, `FALSE` for not-selected). This is enabled by setting
#' `allRows=TRUE` option.
#'
#' Note, however, that this will not work if you use a computed column, like
#' `aes(speed/2, dist))`. Instead, we recommend that you modify the data
#' The `xvar`, `yvar`, `panelvar1`, and `panelvar2`
#' arguments specify which columns in the data correspond to the x variable, y
#' variable, and panel variables of the plot. For example, if your plot is
#' `plot(x=cars$speed, y=cars$dist)`, and your brush is named
#' `"cars_brush"`, then you would use `brushedPoints(cars,
#' input$cars_brush, "speed", "dist")`.
#'
#' For plots created with ggplot2, it should not be necessary to specify the
#' column names; that information will already be contained in the brush,
#' provided that variables are in the original data, and not computed. For
#' example, with `ggplot(cars, aes(x=speed, y=dist)) + geom_point()`, you
#' could use `brushedPoints(cars, input$cars_brush)`. If, however, you use
#' a computed column, like `ggplot(cars, aes(x=speed/2, y=dist)) +
#' geom_point()`, then it will not be able to automatically extract column names
#' and filter on them. If you want to use this function to filter data, it is
#' recommended that you not use computed columns; instead, modify the data
#' first, and then make the plot with "raw" columns in the modified data.
#'
#' @section Brushing:
#' If x or y column is a factor, then it will be coerced to an integer vector.
#' If it is a character vector, then it will be coerced to a factor and then
#' integer vector. This means that the brush will be considered to cover a
#' given character/factor value when it covers the center value.
#' If a specified x or y column is a factor, then it will be coerced to an
#' integer vector. If it is a character vector, then it will be coerced to a
#' factor and then integer vector. This means that the brush will be considered
#' to cover a given character/factor value when it covers the center value.
#'
#' If the brush is operating in just the x or y directions (e.g., with
#' `brushOpts(direction = "x")`, then this function will filter out points
#' using just the x or y variable, whichever is appropriate.
#'
#' @returns
#' A data frame based on `df`, containing the observations selected by the
#' brush or near the click event. For `nearPoints()`, the rows will be sorted
#' by distance to the event.
#'
#' If `allRows = TRUE`, then all rows will returned, along with a new
#' `selected_` column that indicates whether or not the point was selected.
#' The output from `nearPoints()` will no longer be sorted, but you can
#' set `addDist = TRUE` to get an additional column that gives the pixel
#' distance to the pointer.
#'
#' @param brush The data from a brush, such as `input$plot_brush`.
#' @param df A data frame from which to select rows.
#' @param brush,coordinfo The data from a brush or click/dblclick/hover event
#' e.g. `input$plot_brush`, `input$plot_click`.
#' @param xvar,yvar A string giving the name of the variable on the x or y axis.
#' These are only required for base graphics, and must be the name of
#' a column in `df`.
#' @param panelvar1,panelvar2 A string giving the name of a panel variable.
#' For expert use only; in most cases these will be automatically
#' derived from the ggplot2 spec.
#' @param xvar,yvar A string with the name of the variable on the x or y axis.
#' This must also be the name of a column in `df`. If absent, then this
#' function will try to infer the variable from the brush (only works for
#' ggplot2).
#' @param panelvar1,panelvar2 Each of these is a string with the name of a panel
#' variable. For example, if with ggplot2, you facet on a variable called
#' `cyl`, then you can use `"cyl"` here. However, specifying the
#' panel variable should not be necessary with ggplot2; Shiny should be able
#' to auto-detect the panel variable.
#' @param allRows If `FALSE` (the default) return a data frame containing
#' the selected rows. If `TRUE`, the input data frame will have a new
#' column, `selected_`, which indicates whether the row was selected or not.
#' @param threshold A maximum distance (in pixels) to the pointer location.
#' Rows in the data frame will be selected if the distance to the pointer is
#' less than `threshold`.
#' @param maxpoints Maximum number of rows to return. If `NULL` (the default),
#' will return all rows within the threshold distance.
#' @param addDist If TRUE, add a column named `dist_` that contains the
#' distance from the coordinate to the point, in pixels. When no pointer
#' event has yet occurred, the value of `dist_` will be `NA`.
#' column, `selected_`, which indicates whether the row was inside the
#' brush (`TRUE`) or outside the brush (`FALSE`).
#'
#' @seealso [plotOutput()] for example usage.
#' @export
#' @examples
#' \dontrun{
#' # Note that in practice, these examples would need to go in reactives
#' # or observers.
#'
#' # This would select all points within 5 pixels of the click
#' nearPoints(mtcars, input$plot_click)
#'
#' # Select just the nearest point within 10 pixels of the click
#' nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
#'
#' }
brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
panelvar1 = NULL, panelvar2 = NULL,
allRows = FALSE) {
@@ -208,8 +191,56 @@ brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
# $ direction: chr "y"
#' @export
#' @rdname brushedPoints
#'Find rows of data that are near a click/hover/double-click
#'
#'This function returns rows from a data frame which are near a click, hover, or
#'double-click, when used with [plotOutput()]. The rows will be sorted
#'by their distance to the mouse event.
#'
#'It is also possible for this function to return all rows from the input data
#'frame, but with an additional column `selected_`, which indicates which
#'rows of the input data frame are selected by the brush (`TRUE` for
#'selected, `FALSE` for not-selected). This is enabled by setting
#'`allRows=TRUE` option. If this is used, the resulting data frame will not
#'be sorted by distance to the mouse event.
#'
#'The `xvar`, `yvar`, `panelvar1`, and `panelvar2` arguments
#'specify which columns in the data correspond to the x variable, y variable,
#'and panel variables of the plot. For example, if your plot is
#'`plot(x=cars$speed, y=cars$dist)`, and your click variable is named
#'`"cars_click"`, then you would use `nearPoints(cars,
#'input$cars_brush, "speed", "dist")`.
#'
#'@inheritParams brushedPoints
#'@param coordinfo The data from a mouse event, such as `input$plot_click`.
#'@param threshold A maxmimum distance to the click point; rows in the data
#' frame where the distance to the click is less than `threshold` will be
#' returned.
#'@param maxpoints Maximum number of rows to return. If NULL (the default),
#' return all rows that are within the threshold distance.
#'@param addDist If TRUE, add a column named `dist_` that contains the
#' distance from the coordinate to the point, in pixels. When no mouse event
#' has yet occured, the value of `dist_` will be `NA`.
#'@param allRows If `FALSE` (the default) return a data frame containing
#' the selected rows. If `TRUE`, the input data frame will have a new
#' column, `selected_`, which indicates whether the row was inside the
#' selected by the mouse event (`TRUE`) or not (`FALSE`).
#'
#'@seealso [plotOutput()] for more examples.
#'
#' @examples
#' \dontrun{
#' # Note that in practice, these examples would need to go in reactives
#' # or observers.
#'
#' # This would select all points within 5 pixels of the click
#' nearPoints(mtcars, input$plot_click)
#'
#' # Select just the nearest point within 10 pixels of the click
#' nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
#'
#' }
#'@export
nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
panelvar1 = NULL, panelvar2 = NULL,
threshold = 5, maxpoints = NULL, addDist = FALSE,

View File

@@ -16,7 +16,7 @@
#'
#' ui <- fluidPage(
#' sliderInput("obs", "Number of observations", 0, 1000, 500),
#' actionButton("goButton", "Go!", class = "btn-success"),
#' actionButton("goButton", "Go!"),
#' plotOutput("distPlot")
#' )
#'
@@ -36,10 +36,6 @@
#'
#' }
#'
#' ## Example of adding extra class values
#' actionButton("largeButton", "Large Primary Button", class = "btn-primary btn-lg")
#' actionLink("infoLink", "Information Link", class = "btn-info")
#'
#' @seealso [observeEvent()] and [eventReactive()]
#'
#' @section Server value:

View File

@@ -311,32 +311,16 @@ HandlerManager <- R6Class("HandlerManager",
},
call = .httpServer(
function (req) {
hybrid_chain(
hybrid_chain(
withCallingHandlers(withLogErrors(handlers$invoke(req)),
error = function(cond) {
sanitizeErrors <- getOption('shiny.sanitize.errors', FALSE)
if (inherits(cond, 'shiny.custom.error') || !sanitizeErrors) {
stop(cond$message, call. = FALSE)
} else {
stop(paste("An error has occurred. Check your logs or",
"contact the app author for clarification."),
call. = FALSE)
}
}
),
catch = function(err) {
httpResponse(status = 500L,
content_type = "text/html",
content = as.character(htmltools::htmlTemplate(
system.file("template", "error.html", package = "shiny"),
message = conditionMessage(err)
))
)
withCallingHandlers(withLogErrors(handlers$invoke(req)),
error = function(cond) {
sanitizeErrors <- getOption('shiny.sanitize.errors', FALSE)
if (inherits(cond, 'shiny.custom.error') || !sanitizeErrors) {
stop(cond$message, call. = FALSE)
} else {
stop(paste("An error has occurred. Check your logs or",
"contact the app author for clarification."),
call. = FALSE)
}
),
function(resp) {
maybeInjectAutoreload(resp)
}
)
},
@@ -406,22 +390,6 @@ HandlerManager <- R6Class("HandlerManager",
)
)
maybeInjectAutoreload <- function(resp) {
if (getOption("shiny.autoreload", FALSE) &&
isTRUE(grepl("^text/html($|;)", resp$content_type)) &&
is.character(resp$content)) {
resp$content <- gsub(
"</head>",
"<script src=\"shared/shiny-autoreload.js\"></script>\n</head>",
resp$content,
fixed = TRUE
)
}
resp
}
# Safely get the Content-Length of a Rook response, or NULL if the length cannot
# be determined for whatever reason (probably malformed response$content).
# If deleteOwnedContent is TRUE, then the function should delete response

View File

@@ -70,9 +70,16 @@ extract <- function(promise) {
}
#' @noRd
mapNames <- function(func, vals) {
names(vals) <- vapply(names(vals), func, character(1))
vals
patchModuleFunction <- function(module) {
body(module) <- rlang::expr({
withr::with_options(base::list(`shiny.allowoutputreads` = TRUE), {
session$setEnv(base::environment())
session$setReturned({
!!!body(module)
})
})
})
module
}
#' Mock Shiny Session
@@ -89,8 +96,6 @@ MockShinySession <- R6Class(
public = list(
#' @field env The environment associated with the session.
env = NULL,
#' @field returned The value returned by the module.
returned = NULL,
#' @field singletons Hardcoded as empty. Needed for rendering HTML (i.e. renderUI)
singletons = character(0),
#' @field clientData Mock client data that always returns a size for plots
@@ -108,7 +113,8 @@ MockShinySession <- R6Class(
userData = NULL,
#' @field progressStack A stack of progress objects
progressStack = 'Stack',
#' @field TRUE when a moduleServer()-based module is under test
isModuleServer = FALSE,
#' @description Create a new MockShinySession
initialize = function() {
private$.input <- ReactiveValues$new(dedupe = FALSE, label = "input")
@@ -189,17 +195,14 @@ MockShinySession <- R6Class(
return(paste('data:', contentType, ';base64,', b64, sep=''))
},
#' @description Sets reactive values associated with the `session$inputs`
#' object and flushes the reactives.
#' @param ... The inputs to set. These arguments are processed with
#' [rlang::list2()] and so are _[dynamic][rlang::dyn-dots]_. Input names
#' may not be duplicated.
#' @description Sets reactive values associated with the `session$inputs` object
#' and flushes the reactives.
#' @param ... The inputs to set.
#' @examples
#' \dontrun{
#' session$setInputs(x=1, y=2)
#' }
#' s <- MockShinySession$new()
#' s$setInputs(x=1, y=2)
setInputs = function(...) {
vals <- rlang::dots_list(..., .homonyms = "error")
vals <- list(...)
mapply(names(vals), vals, FUN = function(name, value) {
private$.input$set(name, value)
})
@@ -382,57 +385,47 @@ MockShinySession <- R6Class(
#' @param export Not used
#' @param format Not used
getTestSnapshotUrl = function(input=TRUE, output=TRUE, export=TRUE, format="json") {},
#' @description Returns the given id prefixed by this namespace's id.
#' @description Returns the given id prefixed by `mock-session-`.
#' @param id The id to modify.
ns = function(id) {
NS(private$nsPrefix, id)
paste0("mock-session-", id) # TODO: does this need to be more complex/intelligent?
},
#' @description Trigger a reactive flush right now.
flushReact = function(){
private$flush()
},
setEnv = function(env) {
self$env <- env
},
setReturned = function(value) {
private$returnedVal <- value
private$flush()
value
},
#' @description Create and return a namespace-specific session proxy.
#' @param namespace Character vector indicating a namespace.
makeScope = function(namespace) {
ns <- NS(namespace)
createSessionProxy(
proxy <- createSessionProxy(
self,
input = .createReactiveValues(private$.input, readonly = TRUE, ns = ns),
output = structure(.createOutputWriter(self, ns = ns), class = "shinyoutput"),
makeScope = function(namespace) self$makeScope(ns(namespace)),
ns = function(namespace) ns(namespace),
env = NULL,
returned = NULL,
setEnv = function(env) assign("env", env, envir = proxy),
setReturned = function(value) {
assign("returned", value, envir = proxy)
private$flush()
value
},
setInputs = function(...) {
self$setInputs(!!!mapNames(ns, rlang::dots_list(..., .homonyms = "error")))
args <- list(...)
names(args) <- ns(names(args))
do.call(self$setInputs, args)
}
)
},
#' @description Set the environment associated with a testServer() call, but
#' only if it has not previously been set. This ensures that only the
#' environment of the outermost module under test is the one retained. In
#' other words, the first assignment wins.
#' @param env The environment to retain.
setEnv = function(env) {
if (is.null(self$env)) {
stopifnot(all(c("input", "output", "session") %in% ls(env)))
self$env <- env
}
},
#' @description Set the value returned by the module call and proactively
#' flush. Note that this method may be called multiple times if modules
#' are nested. The last assignment, corresponding to an invocation of
#' setReturned() in the outermost module, wins.
#' @param value The value returned from the module
setReturned = function(value) {
self$returned <- value
value
},
#' @description Get the value returned by the module call.
getReturned = function() self$returned,
#' @description Return a distinct character identifier for use as a proxy
#' namespace.
genId = function() {
private$idCounter <- private$idCounter + 1
paste0("proxy", private$idCounter)
proxy
}
),
private = list(
@@ -443,8 +436,7 @@ MockShinySession <- R6Class(
timer = NULL,
closed = FALSE,
outs = list(),
nsPrefix = "mock-session",
idCounter = 0,
returnedVal = NULL,
flush = function(){
isolate(private$flushCBs$invoke(..stacktraceon = TRUE))
@@ -454,6 +446,18 @@ MockShinySession <- R6Class(
}
),
active = list(
# If assigning to `returned`, proactively flush
#' @field returned The value returned from the module
returned = function(value){
if(missing(value)){
return(private$returnedVal)
}
# When you assign to returned, that implies that you just ran
# the module. So we should proactively flush. We have to do this
# here since flush is private.
private$returnedVal <- value
private$flush()
},
#' @field request An empty environment where the request should be. The request isn't meaningfully mocked currently.
request = function(value) {
if (!missing(value)){
@@ -464,3 +468,4 @@ MockShinySession <- R6Class(
}
)
)

View File

@@ -36,6 +36,7 @@ createSessionProxy <- function(parentSession, ...) {
`[[<-.session_proxy` <- `$<-.session_proxy`
#' Shiny modules
#'
#' Shiny's module feature lets you break complicated UI and server logic into
@@ -43,9 +44,8 @@ createSessionProxy <- function(parentSession, ...) {
#' modules are easier to reuse and easier to reason about. See the article at
#' <http://shiny.rstudio.com/articles/modules.html> to learn more.
#'
#' Starting in Shiny 1.5.0, we recommend using `moduleServer` instead of
#' `callModule`, because the syntax is a little easier to understand, and
#' modules created with `moduleServer` can be tested with [`testServer()`].
#' Starting in Shiny 1.5.0, we recommend using `moduleFunction` instead of
#' `callModule`, because syntax is a little easier to understand.
#'
#' @param module A Shiny module server function.
#' @param id An ID string that corresponds with the ID used to call the module's
@@ -70,19 +70,16 @@ createSessionProxy <- function(parentSession, ...) {
#'
#' # Define the server logic for a module
#' counterServer <- function(id) {
#' moduleServer(
#' id,
#' function(input, output, session) {
#' count <- reactiveVal(0)
#' observeEvent(input$button, {
#' count(count() + 1)
#' })
#' output$out <- renderText({
#' count()
#' })
#' count
#' }
#' )
#' moduleServer(id, function(input, output, session) {
#' count <- reactiveVal(0)
#' observeEvent(input$button, {
#' count(count() + 1)
#' })
#' output$out <- renderText({
#' count()
#' })
#' count
#' })
#' }
#'
#' # Use the module in an app
@@ -94,9 +91,7 @@ createSessionProxy <- function(parentSession, ...) {
#' counterServer("counter1")
#' counterServer("counter2")
#' }
#' if (interactive()) {
#' shinyApp(ui, server)
#' }
#' shinyApp(ui, server)
#'
#'
#'
@@ -104,19 +99,16 @@ createSessionProxy <- function(parentSession, ...) {
#' # add them to your function. In this case `prefix` is text that will be
#' # printed before the count.
#' counterServer2 <- function(id, prefix = NULL) {
#' moduleServer(
#' id,
#' function(input, output, session) {
#' count <- reactiveVal(0)
#' observeEvent(input$button, {
#' count(count() + 1)
#' })
#' output$out <- renderText({
#' paste0(prefix, count())
#' })
#' count
#' }
#' )
#' moduleServer(id, function(input, output, session) {
#' count <- reactiveVal(0)
#' observeEvent(input$button, {
#' count(count() + 1)
#' })
#' output$out <- renderText({
#' paste0(prefix, count())
#' })
#' count
#' })
#' }
#'
#' ui <- fluidPage(
@@ -125,29 +117,37 @@ createSessionProxy <- function(parentSession, ...) {
#' server <- function(input, output, session) {
#' counterServer2("counter", "The current count is: ")
#' }
#' if (interactive()) {
#' shinyApp(ui, server)
#' }
#' shinyApp(ui, server)
#'
#' @export
moduleServer <- function(id, module, session = getDefaultReactiveDomain()) {
if (inherits(session, "MockShinySession")) {
body(module) <- rlang::expr({
session$setEnv(base::environment())
!!body(module)
})
session$setReturned(callModule(module, id, session = session))
if (inherits(sessionFor(session), "MockShinySession")) {
module <- patchModuleFunction(module)
isolate(callModule(module, id, session = session))
} else {
callModule(module, id, session = session)
}
}
#' @noRd
sessionFor <- function(session) {
if (inherits(session, c("MockShinySession", "ShinySession")))
return(session)
if (!inherits(session, "session_proxy"))
stop("session must be a ShinySession, MockShinySession, or session_proxy object.")
while (inherits(session, "session_proxy"))
session <- session$parent
session
}
#' @rdname moduleServer
#' @export
callModule <- function(module, id, ..., session = getDefaultReactiveDomain()) {
if (!inherits(session, c("ShinySession", "session_proxy", "MockShinySession"))) {
stop("session must be a ShinySession or session_proxy object.")
if (!inherits(session, c("ShinySession", "MockShinySession", "session_proxy"))) {
stop("session must be a ShinySession, MockShinySession, or session_proxy object.")
}
childScope <- session$makeScope(id)

View File

@@ -222,7 +222,7 @@ reactiveVal <- function(value = NULL, label = NULL) {
rv$set(x)
}
},
class = c("reactiveVal", "reactive", "function"),
class = c("reactiveVal", "reactive"),
label = label,
.impl = rv
)
@@ -969,7 +969,7 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
if (length(srcref) >= 2) attr(label, "srcref") <- srcref[[2]]
attr(label, "srcfile") <- srcFileOfRef(srcref[[1]])
o <- Observable$new(fun, label, domain, ..stacktraceon = ..stacktraceon)
structure(o$getValue, observable = o, class = c("reactiveExpr", "reactive", "function"))
structure(o$getValue, observable = o, class = c("reactiveExpr", "reactive"))
}
# Given the srcref to a reactive expression, attempts to figure out what the

View File

@@ -1,195 +0,0 @@
####
# Generated by `./tools/updateReexports.R`: do not edit by hand
# Please call `source('tools/updateReexports.R') from the root folder to update`
####
# fastmap key_missing.Rd -------------------------------------------------------
#' @importFrom fastmap key_missing
#' @export
fastmap::key_missing
#' @importFrom fastmap is.key_missing
#' @export
fastmap::is.key_missing
# htmltools builder.Rd ---------------------------------------------------------
#' @importFrom htmltools tags
#' @export
htmltools::tags
#' @importFrom htmltools p
#' @export
htmltools::p
#' @importFrom htmltools h1
#' @export
htmltools::h1
#' @importFrom htmltools h2
#' @export
htmltools::h2
#' @importFrom htmltools h3
#' @export
htmltools::h3
#' @importFrom htmltools h4
#' @export
htmltools::h4
#' @importFrom htmltools h5
#' @export
htmltools::h5
#' @importFrom htmltools h6
#' @export
htmltools::h6
#' @importFrom htmltools a
#' @export
htmltools::a
#' @importFrom htmltools br
#' @export
htmltools::br
#' @importFrom htmltools div
#' @export
htmltools::div
#' @importFrom htmltools span
#' @export
htmltools::span
#' @importFrom htmltools pre
#' @export
htmltools::pre
#' @importFrom htmltools code
#' @export
htmltools::code
#' @importFrom htmltools img
#' @export
htmltools::img
#' @importFrom htmltools strong
#' @export
htmltools::strong
#' @importFrom htmltools em
#' @export
htmltools::em
#' @importFrom htmltools hr
#' @export
htmltools::hr
# htmltools tag.Rd -------------------------------------------------------------
#' @importFrom htmltools tag
#' @export
htmltools::tag
#' @importFrom htmltools tagList
#' @export
htmltools::tagList
#' @importFrom htmltools tagAppendAttributes
#' @export
htmltools::tagAppendAttributes
#' @importFrom htmltools tagHasAttribute
#' @export
htmltools::tagHasAttribute
#' @importFrom htmltools tagGetAttribute
#' @export
htmltools::tagGetAttribute
#' @importFrom htmltools tagAppendChild
#' @export
htmltools::tagAppendChild
#' @importFrom htmltools tagAppendChildren
#' @export
htmltools::tagAppendChildren
#' @importFrom htmltools tagSetChildren
#' @export
htmltools::tagSetChildren
# htmltools HTML.Rd ------------------------------------------------------------
#' @importFrom htmltools HTML
#' @export
htmltools::HTML
# htmltools include.Rd ---------------------------------------------------------
#' @importFrom htmltools includeHTML
#' @export
htmltools::includeHTML
#' @importFrom htmltools includeText
#' @export
htmltools::includeText
#' @importFrom htmltools includeMarkdown
#' @export
htmltools::includeMarkdown
#' @importFrom htmltools includeCSS
#' @export
htmltools::includeCSS
#' @importFrom htmltools includeScript
#' @export
htmltools::includeScript
# htmltools singleton.Rd -------------------------------------------------------
#' @importFrom htmltools singleton
#' @export
htmltools::singleton
#' @importFrom htmltools is.singleton
#' @export
htmltools::is.singleton
# htmltools validateCssUnit.Rd -------------------------------------------------
#' @importFrom htmltools validateCssUnit
#' @export
htmltools::validateCssUnit
# htmltools htmlTemplate.Rd ----------------------------------------------------
#' @importFrom htmltools htmlTemplate
#' @export
htmltools::htmlTemplate
# htmltools suppressDependencies.Rd --------------------------------------------
#' @importFrom htmltools suppressDependencies
#' @export
htmltools::suppressDependencies
# htmltools withTags.Rd --------------------------------------------------------
#' @importFrom htmltools withTags
#' @export
htmltools::withTags

View File

@@ -889,14 +889,6 @@ find_panel_info_non_api <- function(b, ggplot_format) {
})
}
# Use public API for getting the unit's type (grid::unitType(), added in R 4.0)
# https://github.com/wch/r-source/blob/f9b8a42/src/library/grid/R/unit.R#L179
getUnitType <- function(u) {
tryCatch(
get("unitType", envir = asNamespace("grid"))(u),
error = function(e) attr(u, "unit", exact = TRUE)
)
}
# Given a gtable object, return the x and y ranges (in pixel dimensions)
find_panel_ranges <- function(g, res) {
@@ -912,11 +904,11 @@ find_panel_ranges <- function(g, res) {
if (inherits(x, "unit.list")) {
# For ggplot2 <= 1.0.1
vapply(x, FUN.VALUE = logical(1), function(u) {
isTRUE(getUnitType(u) == "null")
isTRUE(attr(u, "unit", exact = TRUE) == "null")
})
} else {
# For later versions of ggplot2
getUnitType(x) == "null"
attr(x, "unit", exact = TRUE) == "null"
}
}
@@ -956,11 +948,7 @@ find_panel_ranges <- function(g, res) {
# The plotting panels all are 'null' units.
null_sizes <- rep(NA_real_, length(rel_sizes))
# Workaround for `[.unit` forbidding zero-length subsets
# https://github.com/wch/r-source/blob/f9b8a42/src/library/grid/R/unit.R#L448-L450
if (length(null_idx)) {
null_sizes[null_idx] <- as.numeric(rel_sizes[null_idx])
}
null_sizes[null_idx] <- as.numeric(rel_sizes[null_idx])
# Total size allocated for panels is the total image size minus absolute
# (non-panel) elements.

View File

@@ -279,8 +279,6 @@ decodeMessage <- function(data) {
return(mainMessage)
}
autoReloadCallbacks <- Callbacks$new()
createAppHandlers <- function(httpHandlers, serverFuncSource) {
appvars <- new.env()
appvars$server <- NULL
@@ -306,22 +304,6 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
return(TRUE)
}
if (identical(ws$request$PATH_INFO, "/autoreload/")) {
if (!getOption("shiny.autoreload", FALSE)) {
ws$close()
return(TRUE)
}
callbackHandle <- autoReloadCallbacks$register(function() {
ws$send("autoreload")
ws$close()
})
ws$onClose(function() {
callbackHandle()
})
return(TRUE)
}
if (!is.null(getOption("shiny.observer.error", NULL))) {
warning(
call. = FALSE,

View File

@@ -2296,56 +2296,3 @@ ShinyServerTimingRecorder <- R6Class("ShinyServerTimingRecorder",
)
missingOutput <- function(...) req(FALSE)
#' Insert inline Markdown
#'
#' This function accepts
#' [Markdown](https://en.wikipedia.org/wiki/Markdown)-syntax text and returns
#' HTML that may be included in Shiny UIs.
#'
#' Leading whitespace is trimmed from Markdown text with [glue::trim()].
#' Whitespace trimming ensures Markdown is processed correctly even when the
#' call to `markdown()` is indented within surrounding R code.
#'
#' By default, [Github extensions][commonmark::extensions] are enabled, but this
#' can be disabled by passing `extensions = FALSE`.
#'
#' Markdown rendering is performed by [commonmark::markdown_html()]. Additional
#' arguments to `markdown()` are passed as arguments to `markdown_html()`
#'
#' @param mds A character vector of Markdown source to convert to HTML. If the
#' vector has more than one element, a single-element character vector of
#' concatenated HTML is returned.
#' @param extensions Enable Github syntax extensions; defaults to `TRUE`.
#' @param .noWS Character vector used to omit some of the whitespace that would
#' normally be written around generated HTML. Valid options include `before`,
#' `after`, and `outside` (equivalent to `before` and `end`).
#' @param ... Additional arguments to pass to [commonmark::markdown_html()].
#' These arguments are _[dynamic][rlang::dyn-dots]_.
#'
#' @return a character vector marked as HTML.
#' @export
#' @examples
#' ui <- fluidPage(
#' markdown("
#' # Markdown Example
#'
#' This is a markdown paragraph, and will be contained within a `<p>` tag
#' in the UI.
#'
#' The following is an unordered list, which will be represented in the UI as
#' a `<ul>` with `<li>` children:
#'
#' * a bullet
#' * another
#'
#' [Links](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) work;
#' so does *emphasis*.
#'
#' To see more of what's possible, check out [commonmark.org/help](https://commonmark.org/help).
#' ")
#' )
markdown <- function(mds, extensions = TRUE, .noWS = NULL, ...) {
html <- rlang::exec(commonmark::markdown_html, glue::trim(mds), extensions = extensions, ...)
htmltools::HTML(html, .noWS = .noWS)
}

View File

@@ -104,18 +104,20 @@ navTabsDropdown <- function(files) {
tabContentHelper <- function(files, path, language) {
lapply(files, function(file) {
tags$div(class=paste("tab-pane",
with(tags,
div(class=paste("tab-pane",
if (tolower(file) %in% c("app.r", "server.r")) " active"
else "",
sep=""),
id=paste(gsub(".", "_", file, fixed=TRUE),
"_code", sep=""),
tags$pre(class="shiny-code",
pre(class="shiny-code",
# we need to prevent the indentation of <code> ... </code>
HTML(format(tags$code(
class=paste0("language-", language),
paste(readUTF8(file.path.ci(path, file)), collapse="\n")
), indent = FALSE))))
)
})
}

158
R/test-module.R Normal file
View File

@@ -0,0 +1,158 @@
#' Integration testing for Shiny modules or server functions
#'
#' Offer a way to test the reactive interactions in Shiny --- either in Shiny
#' modules or in the server portion of a Shiny application. For more
#' information, visit [the Shiny Dev Center article on integration
#' testing](https://shiny.rstudio.com/articles/integration-testing.html).
#' @param module The module to test
#' @param expr Test code containing expectations. The test expression will run
#' in the module's environment, meaning that the module's parameters (e.g.
#' `input`, `output`, and `session`) will be available along with any other
#' values created inside of the module.
#' @param ... Additional arguments to pass to the module function. These
#' arguments are processed with [rlang::list2()] and so are
#' _[dynamic][rlang::dyn-dots]_.
#' @return The result of evaluating `expr`.
#' @include mock-session.R
#' @rdname testModule
#' @examples
#' module <- function(input, output, session, multiplier = 2, prefix = "I am ") {
#' myreactive <- reactive({
#' input$x * multiplier
#' })
#' output$txt <- renderText({
#' paste0(prefix, myreactive())
#' })
#' }
#'
#' # Basic Usage
#' # -----------
#' testModule(module, {
#' session$setInputs(x = 1)
#' # You're also free to use third-party
#' # testing packages like testthat:
#' # 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.
#' }, multiplier = 2)
#'
#' # Advanced Usage
#' # --------------
#' multiplier_arg_name = "multiplier"
#' more_args <- list(prefix = "I am ")
#' testModule(module, {
#' session$setInputs(x = 1)
#' stopifnot(myreactive() == 2)
#' stopifnot(output$txt == "I am 2")
#' # !!/:= and !!! from rlang are used below to splice computed arguments
#' # into the testModule() argument list.
#' }, !!multiplier_arg_name := 2, !!!more_args)
#' @export
testModule <- function(module, expr, ...) {
.testModule(
module,
quosure = rlang::enquo(expr),
dots = rlang::list2(...),
env = rlang::caller_env()
)
}
isOldModule <- function(func) {
stopifnot(is.function(func))
required <- c("input", "output", "session")
declared <- names(formals(func))
setequal(required, intersect(required, declared))
}
#' @noRd
.testModule <- function(module, quosure, dots, env) {
session <- MockShinySession$new()
on.exit(if (!session$isClosed()) session$close())
if (isOldModule(module)) {
module <- patchModuleFunction(module)
args <- append(dots, list(input = session$input, output = session$output, session = session))
} else {
args <- dots
}
isolate(withReactiveDomain(session, do.call(module, args)))
isolate({
withReactiveDomain(
session,
withr::with_options(list(`shiny.allowoutputreads`=TRUE), {
rlang::eval_tidy(
quosure,
data = rlang::as_data_mask(as.list(session$env)),
env = env
)
})
)
})
}
#' Test an app's server-side logic
#' @param appDir The directory root of the Shiny application. If `NULL`, this function
#' will work up the directory hierarchy --- starting with the current directory ---
#' looking for a directory that contains an `app.R` or `server.R` file.
#' @rdname testModule
#' @export
testServer <- function(expr, appDir=NULL) {
if (is.null(appDir)){
appDir <- findApp()
}
app <- shinyAppDir(appDir)
message("Testing application found in: ", appDir)
server <- app$serverFuncSource()
origwd <- getwd()
setwd(appDir)
on.exit({ setwd(origwd) }, add=TRUE)
# Add `session` argument if not present
fn_formals <- formals(server)
if (! "session" %in% names(fn_formals)) {
fn_formals$session <- bquote()
formals(server) <- fn_formals
}
# Test the server function almost as if it were a module. `dots` is empty
# because server functions never take additional arguments.
.testModule(
server,
quosure = rlang::enquo(expr),
dots = list(),
env = rlang::caller_env()
)
}
findApp <- function(startDir="."){
dir <- normalizePath(startDir)
# The loop will either return or stop() itself.
while (TRUE){
if(file.exists.ci(file.path(dir, "app.R")) || file.exists.ci(file.path(dir, "server.R"))){
return(dir)
}
# Move up a directory
origDir <- dir
dir <- dirname(dir)
# Testing for "root" path can be tricky. OSs differ and on Windows, network shares
# might have a \\ prefix. Easier to just see if we got stuck and abort.
if (dir == origDir){
# We can go no further.
stop("No shiny app was found in ", startDir, " or any of its parent directories")
}
}
}

View File

@@ -1,145 +0,0 @@
#' @noRd
isModuleServer <- function(x) {
is.function(x) && names(formals(x))[1] == "id"
}
#' Reactive testing for Shiny server functions and modules
#'
#' A way to test the reactive interactions in Shiny applications. Reactive
#' interactions are defined in the server function of applications and in
#' modules.
#' @param app The path to an application or module to test. In addition to
#' paths, applications may be represented by any object suitable for coercion
#' to an `appObj` by `as.shiny.appobj`. Application server functions must
#' include a `session` argument in order to be tested. If `app` is `NULL` or
#' not supplied, the nearest enclosing directory that is a Shiny app, starting
#' with the current directory, is used.
#' @param expr Test code containing expectations. The test expression will run
#' in the server function environment, meaning that the parameters of the
#' server function (e.g. `input`, `output`, and `session`) will be available
#' along with any other values created inside of the server function.
#' @param args Additional arguments to pass to the module function.
#' If `app` is a module, and no `id` argument is provided, one will be
#' generated and supplied automatically.
#' @return The result of evaluating `expr`.
#' @include mock-session.R
#' @rdname testServer
#' @examples
#' server <- function(id, multiplier = 2, prefix = "I am ") {
#' moduleServer(id, function(input, output, session) {
#' myreactive <- reactive({
#' input$x * multiplier
#' })
#' output$txt <- renderText({
#' paste0(prefix, myreactive())
#' })
#' })
#' }
#'
#' testServer(server, args = list(multiplier = 2), {
#' session$setInputs(x = 1)
#' # You're also free to use third-party
#' # testing packages like testthat:
#' # 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()) {
require(shiny)
quosure <- rlang::enquo(expr)
session <- getDefaultReactiveDomain()
if (inherits(session, "MockShinySession"))
stop("Test expressions may not call testServer()")
if (inherits(session, "session_proxy")
&& inherits(get("parent", envir = session), "MockShinySession"))
stop("Modules may not call testServer()")
session <- MockShinySession$new()
on.exit(if (!session$isClosed()) session$close())
if (isModuleServer(app)) {
if (!("id" %in% names(args)))
args[["id"]] <- session$genId()
# app is presumed to be a module, and modules may take additional arguments,
# so splice in any args.
isolate(
withReactiveDomain(
session,
withr::with_options(list(`shiny.allowoutputreads` = TRUE), {
rlang::exec(app, !!!args)
})
)
)
# If app is a module, then we must use both the module function's immediate
# environment and also its enclosing environment to construct the mask.
parent_clone <- rlang::env_clone(parent.env(session$env))
clone <- rlang::env_clone(session$env, parent_clone)
mask <- rlang::new_data_mask(clone, parent_clone)
isolate(
withReactiveDomain(
session,
withr::with_options(list(`shiny.allowoutputreads` = TRUE), {
rlang::eval_tidy(quosure, mask, rlang::caller_env())
})
)
)
} else {
if (is.null(app)) {
app <- findEnclosingApp(".")
}
appobj <- as.shiny.appobj(app)
if (!is.null(appobj$onStart))
appobj$onStart()
# Ensure appobj$onStop() is called, and the current directory is restored,
# regardless of whether invoking the server function is successful.
tryCatch({
server <- appobj$serverFuncSource()
if (! "session" %in% names(formals(server)))
stop("Tested application server functions must declare input, output, and session arguments.")
body(server) <- rlang::expr({
session$setEnv(base::environment())
!!!body(server)
})
if (length(args))
stop("Arguments were provided to a server function.")
isolate(
withReactiveDomain(
session,
withr::with_options(list(`shiny.allowoutputreads` = TRUE), {
server(input = session$input, output = session$output, session = session)
})
)
)
}, finally = {
if (!is.null(appobj$onStop))
appobj$onStop()
})
# If app is a server, we use only the server function's immediate
# environment to construct the mask.
mask <- rlang::new_data_mask(rlang::env_clone(session$env))
isolate(
withReactiveDomain(
session,
withr::with_options(list(`shiny.allowoutputreads` = TRUE), {
rlang::eval_tidy(quosure, mask, rlang::caller_env())
})
)
)
}
invisible()
}

297
R/test.R
View File

@@ -1,48 +1,11 @@
#' Creates and returns run result data frame.
#'
#' @param file Name of the test runner file, a character vector of length 1.
#' @param pass Whether or not the test passed, a logical vector of length 1.
#' @param result Value (wrapped in a list) obtained by evaluating `file`.
#' This can also by any errors signaled when evaluating the `file`.
#'
#' @return A 1-row data frame representing a single test run. `result` and
#' is a "list column", or a column that contains list elements.
#' Check to see if the given text is a shinytest
#' Scans for the magic string of `app <- ShinyDriver$new(` as an indicator that this is a shinytest.
#' Brought in from shinytest to avoid having to export this function.
#' @noRd
result_row <- function(file, pass, result) {
stopifnot(is.list(result))
df <- data.frame(
file = file,
pass = pass,
result = I(result),
stringsAsFactors = FALSE
)
class(df) <- c("shiny_runtests", class(df))
df
}
#' Check to see if the given directory contains at least one script, and that
#' all scripts in the directory are shinytest scripts.
#' Scans for the magic string of `app <- ShinyDriver$new(` as an indicator that
#' this is a shinytest.
#' @noRd
is_legacy_shinytest_dir <- function(path){
is_shinytest_script <- function(file) {
if (!file.exists(file)) {
return(FALSE)
}
text <- readLines(file, warn = FALSE)
any(
grepl("app\\s*<-\\s*ShinyDriver\\$new\\(", text, perl=TRUE)
)
}
files <- dir(path, full.names = TRUE)
files <- files[!file.info(files)$isdir]
if (length(files) == 0) {
return(FALSE)
}
all(vapply(files, is_shinytest_script, logical(1)))
isShinyTest <- function(text){
lines <- grepl("app\\s*<-\\s*ShinyDriver\\$new\\(", text, perl=TRUE)
any(lines)
}
#' Runs the tests associated with this Shiny app
@@ -55,210 +18,90 @@ is_legacy_shinytest_dir <- function(path){
#' @param filter If not `NULL`, only tests with file names matching this regular
#' expression will be executed. Matching is performed on the file name
#' including the extension.
#' @param assert Logical value which determines if an error should be thrown if any error is captured.
#' @param envir Parent testing environment in which to base the individual testing environments.
#'
#' @return A data frame classed with the supplemental class `"shiny_runtests"`.
#' The data frame has the following columns:
#'
#' | **Name** | **Type** | **Meaning** |
#' | :-- | :-- | :-- |
#' | `file` | `character(1)` | File name of the runner script in `tests/` that was sourced. |
#' | `pass` | `logical(1)` | Whether or not the runner script signaled an error when sourced. |
#' | `result` | any or `NA` | The return value of the runner |
#'
#' @details Historically, [shinytest](https://rstudio.github.io/shinytest/)
#' recommended placing tests at the top-level of the `tests/` directory.
#' This older folder structure is not supported by runTests.
#' Please see [shinyAppTemplate()] for more details.
#' recommended placing tests at the top-level of the `tests/` directory. In
#' order to support that model, `testApp` first checks to see if the `.R`
#' files in the `tests/` directory are all shinytests; if so, just calls out
#' to [shinytest::testApp()].
#' @export
runTests <- function(
appDir = ".",
filter = NULL,
assert = TRUE,
envir = globalenv()
) {
# similar to runApp()
# Allows shiny's functions to be available in the UI, server, and test code
runTests <- function(appDir=".", filter=NULL){
require(shiny)
testsDir <- file.path(appDir, "tests")
if (!dirExists(testsDir)) {
if (!dirExists(testsDir)){
stop("No tests directory found: ", testsDir)
}
runners <- list.files(testsDir, pattern="\\.r$", ignore.case = TRUE)
if (length(runners) == 0) {
if (length(runners) == 0){
message("No test runners found in ", testsDir)
return(result_row(character(0), logical(0), list()))
return(structure(list(result=NA, files=list()), class="shinytestrun"))
}
if (!is.null(filter)) {
if (!is.null(filter)){
runners <- runners[grepl(filter, runners)]
}
if (length(runners) == 0) {
if (length(runners) == 0){
stop("No test runners matched the given filter: '", filter, "'")
}
# See the @details section of the runTests() docs above for why this branch exists.
if (is_legacy_shinytest_dir(testsDir)) {
stop(
"It appears that the .R files in ", testsDir, " are all shinytests.",
" This is not supported by `shiny::runTests()`.",
"\nPlease see `?shiny::migrateLegacyShinytest` to migrate your shinytest file structure to the new format.",
"\nSee `?shiny::shinyAppTemplate` for an example of the new testing file structure."
)
# Inspect each runner to see if it appears to be a shinytest
isST <- vapply(runners, function(r){
text <- readLines(file.path(testsDir, r), warn = FALSE)
isShinyTest(text)
}, logical(1))
if (all(isST)){
# just call out to shinytest
# We don't need to message/warn here since shinytest already does it.
if (!requireNamespace("shinytest", quietly=TRUE) ){
stop("It appears that the .R files in ", testsDir,
" are all shinytests, but shinytest is not installed.")
}
if (!getOption("shiny.autoload.r", TRUE)) {
warning("You've disabled `shiny.autoload.r` via an option but this is not passed through to shinytest. Consider using a _disable_autoload.R file as described at https://rstd.io/shiny-autoload")
}
sares <- shinytest::testApp(appDir)
res <- list()
lapply(sares$results, function(r){
e <- NA_character_
if (!r$pass){
e <- simpleError("Unknown shinytest error")
}
res[[r$name]] <<- e
})
return(structure(list(result=all(is.na(res)), files=res), class="shinytestrun"))
}
renv <- new.env(parent = envir)
testenv <- new.env(parent=globalenv())
renv <- new.env(parent=testenv)
if (getOption("shiny.autoload.r", TRUE)) {
loadSupport(appDir, renv=renv, globalrenv=testenv)
} else if (file.exists.ci(file.path(appDir, "server.R"))){
# then check for global.R to load
if (file.exists(file.path.ci(appDir, "global.R"))){
sourceUTF8(file.path.ci(appDir, "global.R"))
}
}
oldwd <- getwd()
on.exit({
setwd(oldwd)
}, add=TRUE)
setwd(testsDir)
# Otherwise source all the runners -- each in their own environment.
ret <- do.call(rbind, lapply(runners, function(r) {
pass <- FALSE
result <-
tryCatch({
env <- new.env(parent = renv)
withr::with_dir(testsDir, {
ret <- sourceUTF8(r, envir = env)
})
pass <- TRUE
ret
}, error = function(err) {
message("Error in ", r, "\n", err)
err
})
result_row(file.path(testsDir, r), pass, list(result))
}))
fileResults <- list()
lapply(runners, function(r){
env <- new.env(parent=renv)
tryCatch({sourceUTF8(r, envir=env); fileResults[[r]] <<- NA_character_}, error=function(e){
fileResults[[r]] <<- e
})
})
if (isTRUE(assert)) {
if (!all(ret$pass)) {
stop("Shiny App Test Failures detected in\n", paste0("* ", runtest_pretty_file(ret$file[!ret$pass]), collapse = "\n"), call. = FALSE)
}
}
ret
}
runtest_pretty_file <- function(f) {
test_folder <- dirname(f)
app_folder <- dirname(test_folder)
file.path(
basename(app_folder),
basename(test_folder),
basename(f)
)
}
print.shiny_runtests <- function(x, ..., reporter = "summary") {
cat("Shiny App Test Results\n")
if (any(x$pass)) {
# TODO in future... use clisymbols::symbol$tick and crayon green
cat("* Success\n")
mapply(
x$file,
x$pass,
x$result,
FUN = function(file, pass, result) {
if (!pass) return()
# print(result)
cat(" - ", runtest_pretty_file(file), "\n", sep = "")
}
)
}
if (any(!x$pass)) {
# TODO in future... use clisymbols::symbol$cross and crayon red
cat("* Failure\n")
mapply(
x$file,
x$pass,
x$result,
FUN = function(file, pass, result) {
if (pass) return()
cat(" - ", runtest_pretty_file(file), "\n", sep = "")
}
)
}
invisible(x)
}
#' Migrate legacy \pkg{shinytest} files to new test directory structure
#'
#' This function migrates the old-style directory structure used by
#' \pkg{shinytest} to (versions 1.3.1 and below) new test directory structure
#' used in Shiny 1.5.0 and above.
#'
#' In Shiny 1.5.0, the [runTests()] function was added, and it will run test
#' scripts tests/ subdirectory of the application. The directory structure will
#' look something like this:
#'
#' ```
#' appdir/
#' |- R
#' |- tests
#' |- shinytest.R
#' |- shinytest
#' | `- mytest.R
#' |- testthat.R
#' `- testthat
#' `- test-script.R
#' ```
#'
#' This allows for tests using the \pkg{shinytest} package as well as other
#' testing tools, such as the [testServer()] function, which can be used for
#' testing module and server logic, and for unit tests of functions in an R/
#' subdirectory.
#'
#'
#' With the \pkg{shinytest} package, in versions 1.3.0 and below, the tests/
#' subdirectory of the application was used specifically for \pkg{shinytest},
#' and could not be used for other types of tests. So the directory structure
#' would look like this:
#'
#' ```
#' appdir/
#' `- tests
#' `- mytest.R
#' ```
#'
#' In \pkg{shinytest} 1.4.0 and above, it defaults to the new directory
#' structure.
#'
#' @param appdir A directory containing a Shiny application.
#' @param prompt If \code{TRUE}, ask for confirmation when moving files.
#'
#' @export
migrateLegacyShinytest <- function(appdir, prompt = interactive()) {
appdir <- findEnclosingApp(appdir)
test_dir <- file.path(appdir, "tests")
shinytest_dir <- file.path(test_dir, "shinytest")
if (!is_legacy_shinytest_dir(test_dir)) {
stop("The .R files in ", test_dir,
" must all be test scripts for the shinytest package.")
}
if (prompt) {
res <- readline(
paste0(
"Creating ", shinytest_dir, " and moving files from\n", test_dir,
" into it.\nAre you sure you want to do this? [y/n]\n"
)
)
if (!identical(tolower(res), "y")) {
return(invisible())
}
}
files <- dir(test_dir)
dir.create(shinytest_dir, showWarnings = FALSE)
file.rename(file.path(test_dir, files), file.path(shinytest_dir, files))
invisible()
return(structure(list(result=all(is.na(fileResults)), files=fileResults), class="shinytestrun"))
}

View File

@@ -126,15 +126,13 @@ updateCheckboxInput <- function(session, inputId, label = NULL, value = NULL) {
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' actionButton("update", "Update other buttons and link"),
#' actionButton("update", "Update other buttons"),
#' br(),
#' actionButton("goButton", "Go"),
#' br(),
#' actionButton("goButton2", "Go 2", icon = icon("area-chart")),
#' br(),
#' actionButton("goButton3", "Go 3"),
#' br(),
#' actionLink("goLink", "Go Link")
#' actionButton("goButton3", "Go 3")
#' )
#'
#' server <- function(input, output, session) {
@@ -155,26 +153,17 @@ updateCheckboxInput <- function(session, inputId, label = NULL, value = NULL) {
#' # unchaged and changes its label
#' updateActionButton(session, "goButton3",
#' label = "New label 3")
#'
#' # Updates goLink's label and icon
#' updateActionButton(session, "goLink",
#' label = "New link label",
#' icon = icon("link"))
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @rdname updateActionButton
#' @export
updateActionButton <- function(session, inputId, label = NULL, icon = NULL) {
if (!is.null(icon)) icon <- as.character(validateIcon(icon))
message <- dropNulls(list(label=label, icon=icon))
session$sendInputMessage(inputId, message)
}
#' @rdname updateActionButton
#' @export
updateActionLink <- updateActionButton
#' Change the value of a date input on the client

View File

@@ -316,15 +316,6 @@ resolve <- function(dir, relpath) {
return(abs.path)
}
# Given a string, make sure it has a trailing slash.
ensure_trailing_slash <- function(path) {
if (!grepl("/$", path)) {
path <- paste0(path, "/")
}
path
}
isWindows <- function() .Platform$OS.type == 'windows'
# This is a wrapper for download.file and has the same interface.
@@ -1821,55 +1812,3 @@ cat_line <- function(...) {
cat(paste(..., "\n", collapse = ""))
}
select_menu <- function(choices, title = NULL, msg = "Enter one or more numbers (with spaces), or an empty line to exit: \n")
{
if (!is.null(title)) {
cat(title, "\n", sep = "")
}
nc <- length(choices)
op <- paste0(format(seq_len(nc)), ": ", choices)
fop <- format(op)
cat("", fop, "", sep = "\n")
repeat {
answer <- readline(msg)
answer <- strsplit(answer, "[ ,]+")[[1]]
if (all(answer %in% seq_along(choices))) {
return(choices[as.integer(answer)])
}
}
}
#' @noRd
isAppDir <- function(path) {
if (file.exists(file.path.ci(path, "app.R")))
return(TRUE)
if (file.exists(file.path.ci(path, "server.R"))
&& file.exists(file.path.ci(path, "ui.R")))
return(TRUE)
FALSE
}
# Borrowed from rprojroot which borrowed from devtools
#' @noRd
is_root <- function(path) {
identical(
normalizePath(path, winslash = "/"),
normalizePath(dirname(path), winslash = "/")
)
}
#' @noRd
findEnclosingApp <- function(path = ".") {
orig_path <- path
path <- normalizePath(path, winslash = "/", mustWork = TRUE)
repeat {
if (isAppDir(path))
return(path)
if (is_root(path))
stop("Shiny app not found at ", orig_path, " or in any parent directory.")
path <- dirname(path)
}
}

View File

@@ -98,7 +98,6 @@ reference:
- bootstrapLib
- suppressDependencies
- insertUI
- markdown
- title: Rendering functions
desc: Functions that you use in your application's server side code, assigning them to outputs that appear in your user interface.
contents:
@@ -168,8 +167,6 @@ reference:
- title: Utility functions
desc: Miscellaneous utilities that may be useful to advanced users or when extending Shiny.
contents:
- shinyAppTemplate
- migrateLegacyShinytest
- req
- validate
- session
@@ -196,7 +193,7 @@ reference:
- onStop
- diskCache
- memoryCache
- key_missing
- reexports
- title: Plot interaction
desc: Functions related to interactive plots
contents:
@@ -205,6 +202,7 @@ reference:
- clickOpts
- dblclickOpts
- hoverOpts
- nearPoints
- title: Modules
desc: Functions for modularizing Shiny apps
contents:
@@ -219,5 +217,5 @@ reference:
desc: Functions intended for testing of Shiny components
contents:
- runTests
- testServer
- testModule
- MockShinySession

View File

@@ -1,27 +0,0 @@
mymoduleUI <- function(id, label = "Counter") {
# Al uses of Shiny input/output IDs in the UI must be namespaced,
# as in ns("x").
ns <- NS(id)
tagList(
actionButton(ns("button"), label = label),
verbatimTextOutput(ns("out"))
)
}
mymoduleServer <- function(id) {
# moduleServer() wraps a function to create the server component of a
# module.
moduleServer(
id,
function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
count()
})
count
}
)
}

View File

@@ -1,5 +0,0 @@
# Given a numeric vector, convert to strings, sort, and convert back to
# numeric.
lexical_sort <- function(x) {
as.numeric(sort(as.character(x)))
}

View File

@@ -1,52 +0,0 @@
ui <- fluidPage(
{{
# These blocks of code are processed with htmlTemplate()
if (isTRUE(module)) {
' # ======== Modules ========
# mymoduleUI is defined in R/my-module.R
mymoduleUI("mymodule1", "Click counter #1"),
mymoduleUI("mymodule2", "Click counter #2"),
# =========================
'
}
}}
wellPanel(
sliderInput("size", "Data size", min = 5, max = 20, value = 10),
{{
if (isTRUE(rdir)) {
' div("Lexically sorted sequence:"),'
} else {
' div("Sorted sequence:"),'
}
}}
verbatimTextOutput("sequence")
)
)
server <- function(input, output, session) {
{{
if (isTRUE(module)) {
' # ======== Modules ========
# mymoduleServer is defined in R/my-module.R
mymoduleServer("mymodule1")
mymoduleServer("mymodule2")
# =========================
'
}
}}
data <- reactive({
{{
if (isTRUE(rdir)) {
' # lexical_sort from R/sort.R
lexical_sort(seq_len(input$size))'
} else {
' sort(seq_len(input$size))'
}
}}
})
output$sequence <- renderText({
paste(data(), collapse = " ")
})
}
shinyApp(ui, server)

View File

@@ -1,3 +0,0 @@
library(shinytest)
shinytest::testApp("../")

View File

@@ -1,12 +0,0 @@
app <- ShinyDriver$new("../../")
app$snapshotInit("mytest")
app$snapshot()
{{
if (isTRUE(module)) {
'
app$setInputs(`mymodule1-button` = "click")
app$setInputs(`mymodule1-button` = "click")
app$snapshot()'
}
}}

View File

@@ -1,9 +0,0 @@
library(testthat)
test_dir(
"./testthat",
# Run in the app's environment containing all support methods.
env = shiny::loadSupport(),
# Display the regular progress output and throw an error if any test error is found
reporter = c("progress", "fail")
)

View File

@@ -1,18 +0,0 @@
context("mymoduleServer")
# See ?testServer for more information
testServer(mymoduleServer, {
# Set initial value of a button
session$setInputs(button = 0)
# Check the value of the reactiveVal `count()`
expect_equal(count(), 1)
# Check the value of the renderText()
expect_equal(output$out, "1")
# Simulate a click
session$setInputs(button = 1)
expect_equal(count(), 2)
expect_equal(output$out, "2")
})

View File

@@ -1,20 +0,0 @@
context("app")
testServer(expr = {
# Set the `size` slider and check the output
session$setInputs(size = 6)
expect_equal(output$sequence, "1 2 3 4 5 6")
{{
if (isTRUE(rdir)) {
'
session$setInputs(size = 12)
expect_equal(output$sequence, "1 10 11 12 2 3 4 5 6 7 8 9")
'
} else {
'
session$setInputs(size = 12)
expect_equal(output$sequence, "1 2 3 4 5 6 7 8 9 10 11 12")
'
}
}}
})

View File

@@ -1,7 +0,0 @@
# Test the lexical_sort function from R/utils.R
context("sort")
test_that("Lexical sorting works", {
expect_equal(lexical_sort(c(1, 2, 3)), c(1, 2, 3))
expect_equal(lexical_sort(c(1, 2, 3, 13, 11, 21)), c(1, 11, 13, 2, 21, 3))
})

View File

@@ -1,13 +0,0 @@
<html>
<head>
<title>An error has occurred</title>
</head>
<body>
<h1>An error has occurred!</h1>
<p>{{message}}</p>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +0,0 @@
(function() {
var protocol = 'ws:';
if (window.location.protocol === 'https:')
protocol = 'wss:';
var defaultPath = window.location.pathname;
if (!/\/$/.test(defaultPath))
defaultPath += '/';
defaultPath += 'autoreload/';
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
ws.onmessage = function(event) {
if (event.data === "autoreload") {
window.location.reload()
}
}
})();

View File

@@ -407,10 +407,3 @@ pre.shiny-text-output {
color: #aaa;
cursor: not-allowed;
}
/* Hidden tabPanels */
.nav-hidden {
/* override anything bootstrap sets for `.nav` */
display: none !important;
}

File diff suppressed because it is too large Load Diff

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

29
man/HTML.Rd Normal file
View File

@@ -0,0 +1,29 @@
\name{HTML}
\alias{HTML}
\title{Mark Characters as HTML}
\usage{
HTML(text, ..., .noWS = NULL)
}
\arguments{
\item{text}{The text value to mark with HTML}
\item{...}{Any additional values to be converted to character and
concatenated together}
\item{.noWS}{Character vector used to omit some of the whitespace that would
normally be written around this HTML. Valid options include \code{before},
\code{after}, and \code{outside} (equivalent to \code{before} and
\code{end}).}
}
\value{
The same value, but marked as HTML.
}
\description{
Marks the given text as HTML, which means the \link{tag} functions will know
not to perform HTML escaping on it.
}
\examples{
el <- div(HTML("I like <u>turtles</u>"))
cat(as.character(el))
}

View File

@@ -13,17 +13,14 @@ provided to Shiny server functions or modules.
## Method `MockShinySession$setInputs`
## ------------------------------------------------
\dontrun{
session$setInputs(x=1, y=2)
}
s <- MockShinySession$new()
s$setInputs(x=1, y=2)
}
\section{Public fields}{
\if{html}{\out{<div class="r6-fields">}}
\describe{
\item{\code{env}}{The environment associated with the session.}
\item{\code{returned}}{The value returned by the module.}
\item{\code{singletons}}{Hardcoded as empty. Needed for rendering HTML (i.e. renderUI)}
\item{\code{clientData}}{Mock client data that always returns a size for plots}
@@ -41,6 +38,8 @@ session$setInputs(x=1, y=2)
\section{Active bindings}{
\if{html}{\out{<div class="r6-active-bindings">}}
\describe{
\item{\code{returned}}{The value returned from the module}
\item{\code{request}}{An empty environment where the request should be. The request isn't meaningfully mocked currently.}
}
\if{html}{\out{</div>}}
@@ -84,16 +83,11 @@ session$setInputs(x=1, y=2)
\item \href{#method-ns}{\code{MockShinySession$ns()}}
\item \href{#method-flushReact}{\code{MockShinySession$flushReact()}}
\item \href{#method-makeScope}{\code{MockShinySession$makeScope()}}
\item \href{#method-setEnv}{\code{MockShinySession$setEnv()}}
\item \href{#method-setReturned}{\code{MockShinySession$setReturned()}}
\item \href{#method-getReturned}{\code{MockShinySession$getReturned()}}
\item \href{#method-genId}{\code{MockShinySession$genId()}}
\item \href{#method-clone}{\code{MockShinySession$clone()}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-reactlog"></a>}}
\if{latex}{\out{\hypertarget{method-reactlog}{}}}
\subsection{Method \code{reactlog()}}{
No-op
\subsection{Usage}{
@@ -110,7 +104,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-incrementBusyCount"></a>}}
\if{latex}{\out{\hypertarget{method-incrementBusyCount}{}}}
\subsection{Method \code{incrementBusyCount()}}{
No-op
\subsection{Usage}{
@@ -120,7 +113,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-new"></a>}}
\if{latex}{\out{\hypertarget{method-new}{}}}
\subsection{Method \code{new()}}{
Create a new MockShinySession
\subsection{Usage}{
@@ -130,7 +122,6 @@ Create a new MockShinySession
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onFlush"></a>}}
\if{latex}{\out{\hypertarget{method-onFlush}{}}}
\subsection{Method \code{onFlush()}}{
Define a callback to be invoked before a reactive flush
\subsection{Usage}{
@@ -149,7 +140,6 @@ Define a callback to be invoked before a reactive flush
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onFlushed"></a>}}
\if{latex}{\out{\hypertarget{method-onFlushed}{}}}
\subsection{Method \code{onFlushed()}}{
Define a callback to be invoked after a reactive flush
\subsection{Usage}{
@@ -168,7 +158,6 @@ Define a callback to be invoked after a reactive flush
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onEnded"></a>}}
\if{latex}{\out{\hypertarget{method-onEnded}{}}}
\subsection{Method \code{onEnded()}}{
Define a callback to be invoked when the session ends
\subsection{Usage}{
@@ -185,7 +174,6 @@ Define a callback to be invoked when the session ends
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-isEnded"></a>}}
\if{latex}{\out{\hypertarget{method-isEnded}{}}}
\subsection{Method \code{isEnded()}}{
Returns \code{FALSE} if the session has not yet been closed
\subsection{Usage}{
@@ -195,7 +183,6 @@ Returns \code{FALSE} if the session has not yet been closed
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-isClosed"></a>}}
\if{latex}{\out{\hypertarget{method-isClosed}{}}}
\subsection{Method \code{isClosed()}}{
Returns \code{FALSE} if the session has not yet been closed
\subsection{Usage}{
@@ -205,7 +192,6 @@ Returns \code{FALSE} if the session has not yet been closed
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-close"></a>}}
\if{latex}{\out{\hypertarget{method-close}{}}}
\subsection{Method \code{close()}}{
Closes the session
\subsection{Usage}{
@@ -215,7 +201,6 @@ Closes the session
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-cycleStartAction"></a>}}
\if{latex}{\out{\hypertarget{method-cycleStartAction}{}}}
\subsection{Method \code{cycleStartAction()}}{
Unsophisticated mock implementation that merely invokes
the given callback immediately.
@@ -233,7 +218,6 @@ the given callback immediately.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-fileUrl"></a>}}
\if{latex}{\out{\hypertarget{method-fileUrl}{}}}
\subsection{Method \code{fileUrl()}}{
Base64-encode the given file. Needed for image rendering.
\subsection{Usage}{
@@ -254,10 +238,9 @@ Base64-encode the given file. Needed for image rendering.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-setInputs"></a>}}
\if{latex}{\out{\hypertarget{method-setInputs}{}}}
\subsection{Method \code{setInputs()}}{
Sets reactive values associated with the \code{session$inputs}
object and flushes the reactives.
Sets reactive values associated with the \code{session$inputs} object
and flushes the reactives.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{MockShinySession$setInputs(...)}\if{html}{\out{</div>}}
}
@@ -265,17 +248,14 @@ object and flushes the reactives.
\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{...}}{The inputs to set. These arguments are processed with
\code{\link[rlang:list2]{rlang::list2()}} and so are \emph{\link[rlang:dyn-dots]{dynamic}}. Input names
may not be duplicated.}
\item{\code{...}}{The inputs to set.}
}
\if{html}{\out{</div>}}
}
\subsection{Examples}{
\if{html}{\out{<div class="r example copy">}}
\preformatted{\dontrun{
session$setInputs(x=1, y=2)
}
\preformatted{s <- MockShinySession$new()
s$setInputs(x=1, y=2)
}
\if{html}{\out{</div>}}
@@ -284,7 +264,6 @@ session$setInputs(x=1, y=2)
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-.scheduleTask"></a>}}
\if{latex}{\out{\hypertarget{method-.scheduleTask}{}}}
\subsection{Method \code{.scheduleTask()}}{
An internal method which shouldn't be used by others.
\subsection{Usage}{
@@ -303,7 +282,6 @@ An internal method which shouldn't be used by others.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-elapse"></a>}}
\if{latex}{\out{\hypertarget{method-elapse}{}}}
\subsection{Method \code{elapse()}}{
Simulate the passing of time by the given number of milliseconds.
\subsection{Usage}{
@@ -320,7 +298,6 @@ Simulate the passing of time by the given number of milliseconds.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-.now"></a>}}
\if{latex}{\out{\hypertarget{method-.now}{}}}
\subsection{Method \code{.now()}}{
An internal method which shouldn't be used by others.
\subsection{Usage}{
@@ -330,7 +307,6 @@ An internal method which shouldn't be used by others.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-defineOutput"></a>}}
\if{latex}{\out{\hypertarget{method-defineOutput}{}}}
\subsection{Method \code{defineOutput()}}{
An internal method which shouldn't be used by others.
\subsection{Usage}{
@@ -351,7 +327,6 @@ An internal method which shouldn't be used by others.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getOutput"></a>}}
\if{latex}{\out{\hypertarget{method-getOutput}{}}}
\subsection{Method \code{getOutput()}}{
An internal method which shouldn't be used by others.
\subsection{Usage}{
@@ -368,7 +343,6 @@ An internal method which shouldn't be used by others.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-registerDataObj"></a>}}
\if{latex}{\out{\hypertarget{method-registerDataObj}{}}}
\subsection{Method \code{registerDataObj()}}{
No-op
\subsection{Usage}{
@@ -389,7 +363,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-allowReconnect"></a>}}
\if{latex}{\out{\hypertarget{method-allowReconnect}{}}}
\subsection{Method \code{allowReconnect()}}{
No-op
\subsection{Usage}{
@@ -406,7 +379,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-reload"></a>}}
\if{latex}{\out{\hypertarget{method-reload}{}}}
\subsection{Method \code{reload()}}{
No-op
\subsection{Usage}{
@@ -416,7 +388,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-resetBrush"></a>}}
\if{latex}{\out{\hypertarget{method-resetBrush}{}}}
\subsection{Method \code{resetBrush()}}{
No-op
\subsection{Usage}{
@@ -433,7 +404,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-sendCustomMessage"></a>}}
\if{latex}{\out{\hypertarget{method-sendCustomMessage}{}}}
\subsection{Method \code{sendCustomMessage()}}{
No-op
\subsection{Usage}{
@@ -452,7 +422,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-sendBinaryMessage"></a>}}
\if{latex}{\out{\hypertarget{method-sendBinaryMessage}{}}}
\subsection{Method \code{sendBinaryMessage()}}{
No-op
\subsection{Usage}{
@@ -471,7 +440,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-sendInputMessage"></a>}}
\if{latex}{\out{\hypertarget{method-sendInputMessage}{}}}
\subsection{Method \code{sendInputMessage()}}{
No-op
\subsection{Usage}{
@@ -490,7 +458,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-setBookmarkExclude"></a>}}
\if{latex}{\out{\hypertarget{method-setBookmarkExclude}{}}}
\subsection{Method \code{setBookmarkExclude()}}{
No-op
\subsection{Usage}{
@@ -507,7 +474,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getBookmarkExclude"></a>}}
\if{latex}{\out{\hypertarget{method-getBookmarkExclude}{}}}
\subsection{Method \code{getBookmarkExclude()}}{
No-op
\subsection{Usage}{
@@ -517,7 +483,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onBookmark"></a>}}
\if{latex}{\out{\hypertarget{method-onBookmark}{}}}
\subsection{Method \code{onBookmark()}}{
No-op
\subsection{Usage}{
@@ -534,7 +499,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onBookmarked"></a>}}
\if{latex}{\out{\hypertarget{method-onBookmarked}{}}}
\subsection{Method \code{onBookmarked()}}{
No-op
\subsection{Usage}{
@@ -551,7 +515,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-doBookmark"></a>}}
\if{latex}{\out{\hypertarget{method-doBookmark}{}}}
\subsection{Method \code{doBookmark()}}{
No-op
\subsection{Usage}{
@@ -561,7 +524,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onRestore"></a>}}
\if{latex}{\out{\hypertarget{method-onRestore}{}}}
\subsection{Method \code{onRestore()}}{
No-op
\subsection{Usage}{
@@ -578,7 +540,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-onRestored"></a>}}
\if{latex}{\out{\hypertarget{method-onRestored}{}}}
\subsection{Method \code{onRestored()}}{
No-op
\subsection{Usage}{
@@ -595,7 +556,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-exportTestValues"></a>}}
\if{latex}{\out{\hypertarget{method-exportTestValues}{}}}
\subsection{Method \code{exportTestValues()}}{
No-op
\subsection{Usage}{
@@ -605,7 +565,6 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getTestSnapshotUrl"></a>}}
\if{latex}{\out{\hypertarget{method-getTestSnapshotUrl}{}}}
\subsection{Method \code{getTestSnapshotUrl()}}{
No-op
\subsection{Usage}{
@@ -633,9 +592,8 @@ No-op
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-ns"></a>}}
\if{latex}{\out{\hypertarget{method-ns}{}}}
\subsection{Method \code{ns()}}{
Returns the given id prefixed by this namespace's id.
Returns the given id prefixed by \verb{mock-session-}.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{MockShinySession$ns(id)}\if{html}{\out{</div>}}
}
@@ -650,7 +608,6 @@ Returns the given id prefixed by this namespace's id.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-flushReact"></a>}}
\if{latex}{\out{\hypertarget{method-flushReact}{}}}
\subsection{Method \code{flushReact()}}{
Trigger a reactive flush right now.
\subsection{Usage}{
@@ -660,7 +617,6 @@ Trigger a reactive flush right now.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-makeScope"></a>}}
\if{latex}{\out{\hypertarget{method-makeScope}{}}}
\subsection{Method \code{makeScope()}}{
Create and return a namespace-specific session proxy.
\subsection{Usage}{
@@ -674,71 +630,9 @@ Create and return a namespace-specific session proxy.
}
\if{html}{\out{</div>}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-setEnv"></a>}}
\if{latex}{\out{\hypertarget{method-setEnv}{}}}
\subsection{Method \code{setEnv()}}{
Set the environment associated with a testServer() call, but
only if it has not previously been set. This ensures that only the
environment of the outermost module under test is the one retained. In
other words, the first assignment wins.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{MockShinySession$setEnv(env)}\if{html}{\out{</div>}}
}
\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{env}}{The environment to retain.}
}
\if{html}{\out{</div>}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-setReturned"></a>}}
\if{latex}{\out{\hypertarget{method-setReturned}{}}}
\subsection{Method \code{setReturned()}}{
Set the value returned by the module call and proactively
flush. Note that this method may be called multiple times if modules
are nested. The last assignment, corresponding to an invocation of
setReturned() in the outermost module, wins.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{MockShinySession$setReturned(value)}\if{html}{\out{</div>}}
}
\subsection{Arguments}{
\if{html}{\out{<div class="arguments">}}
\describe{
\item{\code{value}}{The value returned from the module}
}
\if{html}{\out{</div>}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getReturned"></a>}}
\if{latex}{\out{\hypertarget{method-getReturned}{}}}
\subsection{Method \code{getReturned()}}{
Get the value returned by the module call.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{MockShinySession$getReturned()}\if{html}{\out{</div>}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-genId"></a>}}
\if{latex}{\out{\hypertarget{method-genId}{}}}
\subsection{Method \code{genId()}}{
Return a distinct character identifier for use as a proxy
namespace.
\subsection{Usage}{
\if{html}{\out{<div class="r">}}\preformatted{MockShinySession$genId()}\if{html}{\out{</div>}}
}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-clone"></a>}}
\if{latex}{\out{\hypertarget{method-clone}{}}}
\subsection{Method \code{clone()}}{
The objects of this class are cloneable with this method.
\subsection{Usage}{

View File

@@ -5,9 +5,7 @@
\alias{NS}
\alias{ns.sep}
\title{Namespaced IDs for inputs/outputs}
\format{
An object of class \code{character} of length 1.
}
\format{An object of class \code{character} of length 1.}
\usage{
NS(namespace, id = NULL)

View File

@@ -74,7 +74,6 @@ shinyApp(ui, server)
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-new"></a>}}
\if{latex}{\out{\hypertarget{method-new}{}}}
\subsection{Method \code{new()}}{
Creates a new progress panel (but does not display it).
\subsection{Usage}{
@@ -108,7 +107,6 @@ is for backward-compatibility).}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-set"></a>}}
\if{latex}{\out{\hypertarget{method-set}{}}}
\subsection{Method \code{set()}}{
Updates the progress panel. When called the first time, the
progress panel is displayed.
@@ -136,7 +134,6 @@ relative to \code{message}.}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-inc"></a>}}
\if{latex}{\out{\hypertarget{method-inc}{}}}
\subsection{Method \code{inc()}}{
Like \code{set}, this updates the progress panel. The difference
is that \code{inc} increases the progress bar by \code{amount}, instead of
@@ -164,7 +161,6 @@ relative to \code{message}.}
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getMin"></a>}}
\if{latex}{\out{\hypertarget{method-getMin}{}}}
\subsection{Method \code{getMin()}}{
Returns the minimum value.
\subsection{Usage}{
@@ -174,7 +170,6 @@ Returns the minimum value.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getMax"></a>}}
\if{latex}{\out{\hypertarget{method-getMax}{}}}
\subsection{Method \code{getMax()}}{
Returns the maximum value.
\subsection{Usage}{
@@ -184,7 +179,6 @@ Returns the maximum value.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-getValue"></a>}}
\if{latex}{\out{\hypertarget{method-getValue}{}}}
\subsection{Method \code{getValue()}}{
Returns the current value.
\subsection{Usage}{
@@ -194,7 +188,6 @@ Returns the current value.
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-close"></a>}}
\if{latex}{\out{\hypertarget{method-close}{}}}
\subsection{Method \code{close()}}{
Removes the progress panel. Future calls to \code{set} and
\code{close} will be ignored.
@@ -205,7 +198,6 @@ Removes the progress panel. Future calls to \code{set} and
}
\if{html}{\out{<hr>}}
\if{html}{\out{<a id="method-clone"></a>}}
\if{latex}{\out{\hypertarget{method-clone}{}}}
\subsection{Method \code{clone()}}{
The objects of this class are cloneable with this method.
\subsection{Usage}{

View File

@@ -43,7 +43,7 @@ if (interactive()) {
ui <- fluidPage(
sliderInput("obs", "Number of observations", 0, 1000, 500),
actionButton("goButton", "Go!", class = "btn-success"),
actionButton("goButton", "Go!"),
plotOutput("distPlot")
)
@@ -63,10 +63,6 @@ shinyApp(ui, server)
}
## Example of adding extra class values
actionButton("largeButton", "Large Primary Button", class = "btn-primary btn-lg")
actionLink("infoLink", "Information Link", class = "btn-info")
}
\seealso{
\code{\link[=observeEvent]{observeEvent()}} and \code{\link[=eventReactive]{eventReactive()}}

View File

@@ -5,7 +5,7 @@
\title{Create an object representing brushing options}
\usage{
brushOpts(
id,
id = NULL,
fill = "#9cf",
stroke = "#036",
opacity = 0.25,

View File

@@ -2,8 +2,7 @@
% Please edit documentation in R/image-interact.R
\name{brushedPoints}
\alias{brushedPoints}
\alias{nearPoints}
\title{Find rows of data selected on an interactive plot.}
\title{Find rows of data that are selected by a brush}
\usage{
brushedPoints(
df,
@@ -14,103 +13,64 @@ brushedPoints(
panelvar2 = NULL,
allRows = FALSE
)
nearPoints(
df,
coordinfo,
xvar = NULL,
yvar = NULL,
panelvar1 = NULL,
panelvar2 = NULL,
threshold = 5,
maxpoints = NULL,
addDist = FALSE,
allRows = FALSE
)
}
\arguments{
\item{df}{A data frame from which to select rows.}
\item{brush, coordinfo}{The data from a brush or click/dblclick/hover event
e.g. \code{input$plot_brush}, \code{input$plot_click}.}
\item{brush}{The data from a brush, such as \code{input$plot_brush}.}
\item{xvar, yvar}{A string giving the name of the variable on the x or y axis.
These are only required for base graphics, and must be the name of
a column in \code{df}.}
\item{xvar, yvar}{A string with the name of the variable on the x or y axis.
This must also be the name of a column in \code{df}. If absent, then this
function will try to infer the variable from the brush (only works for
ggplot2).}
\item{panelvar1, panelvar2}{A string giving the name of a panel variable.
For expert use only; in most cases these will be automatically
derived from the ggplot2 spec.}
\item{panelvar1, panelvar2}{Each of these is a string with the name of a panel
variable. For example, if with ggplot2, you facet on a variable called
\code{cyl}, then you can use \code{"cyl"} here. However, specifying the
panel variable should not be necessary with ggplot2; Shiny should be able
to auto-detect the panel variable.}
\item{allRows}{If \code{FALSE} (the default) return a data frame containing
the selected rows. If \code{TRUE}, the input data frame will have a new
column, \code{selected_}, which indicates whether the row was selected or not.}
\item{threshold}{A maximum distance (in pixels) to the pointer location.
Rows in the data frame will be selected if the distance to the pointer is
less than \code{threshold}.}
\item{maxpoints}{Maximum number of rows to return. If \code{NULL} (the default),
will return all rows within the threshold distance.}
\item{addDist}{If TRUE, add a column named \code{dist_} that contains the
distance from the coordinate to the point, in pixels. When no pointer
event has yet occurred, the value of \code{dist_} will be \code{NA}.}
}
\value{
A data frame based on \code{df}, containing the observations selected by the
brush or near the click event. For \code{nearPoints()}, the rows will be sorted
by distance to the event.
If \code{allRows = TRUE}, then all rows will returned, along with a new
\code{selected_} column that indicates whether or not the point was selected.
The output from \code{nearPoints()} will no longer be sorted, but you can
set \code{addDist = TRUE} to get an additional column that gives the pixel
distance to the pointer.
column, \code{selected_}, which indicates whether the row was inside the
brush (\code{TRUE}) or outside the brush (\code{FALSE}).}
}
\description{
\code{brushedPoints()} returns rows from a data frame which are under a brush.
\code{nearPoints()} returns rows from a data frame which are near a click, hover,
or double-click. Alternatively, set \code{allRows = TRUE} to return all rows from
the input data with an additional column \code{selected_} that indicates which
rows of the would be selected.
This function returns rows from a data frame which are under a brush used
with \code{\link[=plotOutput]{plotOutput()}}.
}
\section{ggplot2}{
\details{
It is also possible for this function to return all rows from the input data
frame, but with an additional column \code{selected_}, which indicates which
rows of the input data frame are selected by the brush (\code{TRUE} for
selected, \code{FALSE} for not-selected). This is enabled by setting
\code{allRows=TRUE} option.
For plots created with ggplot2, it is not necessary to specify the
column names to \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2} as that
information can be automatically derived from the plot specification.
The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2}
arguments specify which columns in the data correspond to the x variable, y
variable, and panel variables of the plot. For example, if your plot is
\code{plot(x=cars$speed, y=cars$dist)}, and your brush is named
\code{"cars_brush"}, then you would use \code{brushedPoints(cars, input$cars_brush, "speed", "dist")}.
Note, however, that this will not work if you use a computed column, like
\verb{aes(speed/2, dist))}. Instead, we recommend that you modify the data
For plots created with ggplot2, it should not be necessary to specify the
column names; that information will already be contained in the brush,
provided that variables are in the original data, and not computed. For
example, with \code{ggplot(cars, aes(x=speed, y=dist)) + geom_point()}, you
could use \code{brushedPoints(cars, input$cars_brush)}. If, however, you use
a computed column, like \code{ggplot(cars, aes(x=speed/2, y=dist)) + geom_point()}, then it will not be able to automatically extract column names
and filter on them. If you want to use this function to filter data, it is
recommended that you not use computed columns; instead, modify the data
first, and then make the plot with "raw" columns in the modified data.
}
\section{Brushing}{
If x or y column is a factor, then it will be coerced to an integer vector.
If it is a character vector, then it will be coerced to a factor and then
integer vector. This means that the brush will be considered to cover a
given character/factor value when it covers the center value.
If a specified x or y column is a factor, then it will be coerced to an
integer vector. If it is a character vector, then it will be coerced to a
factor and then integer vector. This means that the brush will be considered
to cover a given character/factor value when it covers the center value.
If the brush is operating in just the x or y directions (e.g., with
\code{brushOpts(direction = "x")}, then this function will filter out points
using just the x or y variable, whichever is appropriate.
}
\examples{
\dontrun{
# Note that in practice, these examples would need to go in reactives
# or observers.
# This would select all points within 5 pixels of the click
nearPoints(mtcars, input$plot_click)
# Select just the nearest point within 10 pixels of the click
nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
}
}
\seealso{
\code{\link[=plotOutput]{plotOutput()}} for example usage.
}

124
man/builder.Rd Normal file
View File

@@ -0,0 +1,124 @@
\name{builder}
\alias{builder}
\alias{tags}
\alias{p}
\alias{h1}
\alias{h2}
\alias{h3}
\alias{h4}
\alias{h5}
\alias{h6}
\alias{a}
\alias{br}
\alias{div}
\alias{span}
\alias{pre}
\alias{code}
\alias{img}
\alias{strong}
\alias{em}
\alias{hr}
\title{HTML Builder Functions}
\usage{
tags
p(..., .noWS = NULL)
h1(..., .noWS = NULL)
h2(..., .noWS = NULL)
h3(..., .noWS = NULL)
h4(..., .noWS = NULL)
h5(..., .noWS = NULL)
h6(..., .noWS = NULL)
a(..., .noWS = NULL)
br(..., .noWS = NULL)
div(..., .noWS = NULL)
span(..., .noWS = NULL)
pre(..., .noWS = NULL)
code(..., .noWS = NULL)
img(..., .noWS = NULL)
strong(..., .noWS = NULL)
em(..., .noWS = NULL)
hr(..., .noWS = NULL)
}
\arguments{
\item{...}{Attributes and children of the element. Named arguments become
attributes, and positional arguments become children. Valid children are
tags, single-character character vectors (which become text nodes), raw
HTML (see \code{\link{HTML}}), and \code{html_dependency} objects. You can
also pass lists that contain tags, text nodes, or HTML. To use boolean
attributes, use a named argument with a \code{NA} value. (see example)}
\item{.noWS}{A character vector used to omit some of the whitespace that
would normally be written around this tag. Valid options include
\code{before}, \code{after}, \code{outside}, \code{after-begin}, and
\code{before-end}. Any number of these options can be specified.}
}
\description{
Simple functions for constructing HTML documents.
}
\details{
The \code{tags} environment contains convenience functions for all valid
HTML5 tags. To generate tags that are not part of the HTML5 specification,
you can use the \code{\link{tag}()} function.
Dedicated functions are available for the most common HTML tags that do not
conflict with common R functions.
The result from these functions is a tag object, which can be converted using
\code{\link{as.character}()}.
}
\examples{
doc <- tags$html(
tags$head(
tags$title('My first page')
),
tags$body(
h1('My first heading'),
p('My first paragraph, with some ',
strong('bold'),
' text.'),
div(id='myDiv', class='simpleDiv',
'Here is a div with some attributes.')
)
)
cat(as.character(doc))
# create an html5 audio tag with controls.
# controls is a boolean attributes
audio_tag <- tags$audio(
controls = NA,
tags$source(
src = "myfile.wav",
type = "audio/wav"
)
)
cat(as.character(audio_tag))
# suppress the whitespace between tags
oneline <- tags$span(
tags$strong("I'm strong", .noWS="outside")
)
cat(as.character(oneline))
}
\references{
\itemize{
\item W3C html specification about boolean attributes
\url{https://www.w3.org/TR/html5/infrastructure.html#sec-boolean-attributes}
}
}

View File

@@ -4,7 +4,7 @@
\alias{clickOpts}
\title{Create an object representing click options}
\usage{
clickOpts(id, clip = TRUE)
clickOpts(id = NULL, clip = TRUE)
}
\arguments{
\item{id}{Input value name. For example, if the value is \code{"plot_click"},

View File

@@ -4,7 +4,7 @@
\alias{dblclickOpts}
\title{Create an object representing double-click options}
\usage{
dblclickOpts(id, clip = TRUE, delay = 400)
dblclickOpts(id = NULL, clip = TRUE, delay = 400)
}
\arguments{
\item{id}{Input value name. For example, if the value is

View File

@@ -5,7 +5,7 @@
\title{Create an object representing hover options}
\usage{
hoverOpts(
id,
id = NULL,
delay = 300,
delayType = c("debounce", "throttle"),
clip = TRUE,

29
man/htmlTemplate.Rd Normal file
View File

@@ -0,0 +1,29 @@
\name{htmlTemplate}
\alias{htmlTemplate}
\title{Process an HTML template}
\usage{
htmlTemplate(filename = NULL, ..., text_ = NULL, document_ = "auto")
}
\arguments{
\item{filename}{Path to an HTML template file. Incompatible with
\code{text_}.}
\item{...}{Variable values to use when processing the template.}
\item{text_}{A string to use as the template, instead of a file. Incompatible
with \code{filename}.}
\item{document_}{Is this template a complete HTML document (\code{TRUE}), or
a fragment of HTML that is to be inserted into an HTML document
(\code{FALSE})? With \code{"auto"} (the default), auto-detect by searching
for the string \code{"<HTML>"} within the template.}
}
\description{
Process an HTML template and return a tagList object. If the template is a
complete HTML document, then the returned object will also have class
\code{html_document}, and can be passed to the function
\code{\link{renderDocument}} to get the final HTML text.
}
\seealso{
\code{\link{renderDocument}}
}

44
man/include.Rd Normal file
View File

@@ -0,0 +1,44 @@
\name{include}
\alias{include}
\alias{includeHTML}
\alias{includeText}
\alias{includeMarkdown}
\alias{includeCSS}
\alias{includeScript}
\title{Include Content From a File}
\usage{
includeHTML(path)
includeText(path)
includeMarkdown(path)
includeCSS(path, ...)
includeScript(path, ...)
}
\arguments{
\item{path}{The path of the file to be included. It is highly recommended to
use a relative path (the base path being the Shiny application directory),
not an absolute path.}
\item{...}{Any additional attributes to be applied to the generated tag.}
}
\description{
Load HTML, text, or rendered Markdown from a file and turn into HTML.
}
\details{
These functions provide a convenient way to include an extensive amount of
HTML, textual, Markdown, CSS, or JavaScript content, rather than using a
large literal R string.
}
\note{
\code{includeText} escapes its contents, but does no other processing.
This means that hard breaks and multiple spaces will be rendered as they
usually are in HTML: as a single space character. If you are looking for
preformatted text, wrap the call with \code{\link{pre}}, or consider using
\code{includeMarkdown} instead.
The \code{includeMarkdown} function requires the \code{markdown}
package.
}

View File

@@ -5,15 +5,13 @@
\title{Load an app's supporting R files}
\usage{
loadSupport(
appDir = NULL,
appDir,
renv = new.env(parent = globalenv()),
globalrenv = globalenv()
)
}
\arguments{
\item{appDir}{The application directory. If \code{appDir} is \code{NULL} or
not supplied, the nearest enclosing directory that is a Shiny app, starting
with the current directory, is used.}
\item{appDir}{The application directory}
\item{renv}{The environmeny in which the files in the \verb{R/} directory should
be evaluated.}

View File

@@ -1,62 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/shiny.R
\name{markdown}
\alias{markdown}
\title{Insert inline Markdown}
\usage{
markdown(mds, extensions = TRUE, .noWS = NULL, ...)
}
\arguments{
\item{mds}{A character vector of Markdown source to convert to HTML. If the
vector has more than one element, a single-element character vector of
concatenated HTML is returned.}
\item{extensions}{Enable Github syntax extensions; defaults to \code{TRUE}.}
\item{.noWS}{Character vector used to omit some of the whitespace that would
normally be written around generated HTML. Valid options include \code{before},
\code{after}, and \code{outside} (equivalent to \code{before} and \code{end}).}
\item{...}{Additional arguments to pass to \code{\link[commonmark:markdown_html]{commonmark::markdown_html()}}.
These arguments are \emph{\link[rlang:dyn-dots]{dynamic}}.}
}
\value{
a character vector marked as HTML.
}
\description{
This function accepts
\href{https://en.wikipedia.org/wiki/Markdown}{Markdown}-syntax text and returns
HTML that may be included in Shiny UIs.
}
\details{
Leading whitespace is trimmed from Markdown text with \code{\link[glue:trim]{glue::trim()}}.
Whitespace trimming ensures Markdown is processed correctly even when the
call to \code{markdown()} is indented within surrounding R code.
By default, \link[commonmark:extensions]{Github extensions} are enabled, but this
can be disabled by passing \code{extensions = FALSE}.
Markdown rendering is performed by \code{\link[commonmark:markdown_html]{commonmark::markdown_html()}}. Additional
arguments to \code{markdown()} are passed as arguments to \code{markdown_html()}
}
\examples{
ui <- fluidPage(
markdown("
# Markdown Example
This is a markdown paragraph, and will be contained within a `<p>` tag
in the UI.
The following is an unordered list, which will be represented in the UI as
a `<ul>` with `<li>` children:
* a bullet
* another
[Links](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) work;
so does *emphasis*.
To see more of what's possible, check out [commonmark.org/help](https://commonmark.org/help).
")
)
}

View File

@@ -1,48 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/test.R
\name{migrateLegacyShinytest}
\alias{migrateLegacyShinytest}
\title{Migrate legacy \pkg{shinytest} files to new test directory structure}
\usage{
migrateLegacyShinytest(appdir, prompt = interactive())
}
\arguments{
\item{appdir}{A directory containing a Shiny application.}
\item{prompt}{If \code{TRUE}, ask for confirmation when moving files.}
}
\description{
This function migrates the old-style directory structure used by
\pkg{shinytest} to (versions 1.3.1 and below) new test directory structure
used in Shiny 1.5.0 and above.
}
\details{
In Shiny 1.5.0, the \code{\link[=runTests]{runTests()}} function was added, and it will run test
scripts tests/ subdirectory of the application. The directory structure will
look something like this:\preformatted{appdir/
|- R
|- tests
|- shinytest.R
|- shinytest
| `- mytest.R
|- testthat.R
`- testthat
`- test-script.R
}
This allows for tests using the \pkg{shinytest} package as well as other
testing tools, such as the \code{\link[=testServer]{testServer()}} function, which can be used for
testing module and server logic, and for unit tests of functions in an R/
subdirectory.
With the \pkg{shinytest} package, in versions 1.3.0 and below, the tests/
subdirectory of the application was used specifically for \pkg{shinytest},
and could not be used for other types of tests. So the directory structure
would look like this:\preformatted{appdir/
`- tests
`- mytest.R
}
In \pkg{shinytest} 1.4.0 and above, it defaults to the new directory
structure.
}

View File

@@ -31,9 +31,8 @@ modules are easier to reuse and easier to reason about. See the article at
\url{http://shiny.rstudio.com/articles/modules.html} to learn more.
}
\details{
Starting in Shiny 1.5.0, we recommend using \code{moduleServer} instead of
\code{callModule}, because the syntax is a little easier to understand, and
modules created with \code{moduleServer} can be tested with \code{\link[=testServer]{testServer()}}.
Starting in Shiny 1.5.0, we recommend using \code{moduleFunction} instead of
\code{callModule}, because syntax is a little easier to understand.
}
\examples{
# Define the UI for a module
@@ -47,19 +46,16 @@ counterUI <- function(id, label = "Counter") {
# Define the server logic for a module
counterServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
count()
})
count
}
)
moduleServer(id, function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
count()
})
count
})
}
# Use the module in an app
@@ -71,9 +67,7 @@ server <- function(input, output, session) {
counterServer("counter1")
counterServer("counter2")
}
if (interactive()) {
shinyApp(ui, server)
}
shinyApp(ui, server)
@@ -81,19 +75,16 @@ if (interactive()) {
# add them to your function. In this case `prefix` is text that will be
# printed before the count.
counterServer2 <- function(id, prefix = NULL) {
moduleServer(
id,
function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
paste0(prefix, count())
})
count
}
)
moduleServer(id, function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
paste0(prefix, count())
})
count
})
}
ui <- fluidPage(
@@ -102,9 +93,7 @@ ui <- fluidPage(
server <- function(input, output, session) {
counterServer2("counter", "The current count is: ")
}
if (interactive()) {
shinyApp(ui, server)
}
shinyApp(ui, server)
}
\seealso{

97
man/nearPoints.Rd Normal file
View File

@@ -0,0 +1,97 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/image-interact.R
\name{nearPoints}
\alias{nearPoints}
\title{Find rows of data that are near a click/hover/double-click}
\usage{
nearPoints(
df,
coordinfo,
xvar = NULL,
yvar = NULL,
panelvar1 = NULL,
panelvar2 = NULL,
threshold = 5,
maxpoints = NULL,
addDist = FALSE,
allRows = FALSE
)
}
\arguments{
\item{df}{A data frame from which to select rows.}
\item{coordinfo}{The data from a mouse event, such as \code{input$plot_click}.}
\item{xvar}{A string with the name of the variable on the x or y axis.
This must also be the name of a column in \code{df}. If absent, then this
function will try to infer the variable from the brush (only works for
ggplot2).}
\item{yvar}{A string with the name of the variable on the x or y axis.
This must also be the name of a column in \code{df}. If absent, then this
function will try to infer the variable from the brush (only works for
ggplot2).}
\item{panelvar1}{Each of these is a string with the name of a panel
variable. For example, if with ggplot2, you facet on a variable called
\code{cyl}, then you can use \code{"cyl"} here. However, specifying the
panel variable should not be necessary with ggplot2; Shiny should be able
to auto-detect the panel variable.}
\item{panelvar2}{Each of these is a string with the name of a panel
variable. For example, if with ggplot2, you facet on a variable called
\code{cyl}, then you can use \code{"cyl"} here. However, specifying the
panel variable should not be necessary with ggplot2; Shiny should be able
to auto-detect the panel variable.}
\item{threshold}{A maxmimum distance to the click point; rows in the data
frame where the distance to the click is less than \code{threshold} will be
returned.}
\item{maxpoints}{Maximum number of rows to return. If NULL (the default),
return all rows that are within the threshold distance.}
\item{addDist}{If TRUE, add a column named \code{dist_} that contains the
distance from the coordinate to the point, in pixels. When no mouse event
has yet occured, the value of \code{dist_} will be \code{NA}.}
\item{allRows}{If \code{FALSE} (the default) return a data frame containing
the selected rows. If \code{TRUE}, the input data frame will have a new
column, \code{selected_}, which indicates whether the row was inside the
selected by the mouse event (\code{TRUE}) or not (\code{FALSE}).}
}
\description{
This function returns rows from a data frame which are near a click, hover, or
double-click, when used with \code{\link[=plotOutput]{plotOutput()}}. The rows will be sorted
by their distance to the mouse event.
}
\details{
It is also possible for this function to return all rows from the input data
frame, but with an additional column \code{selected_}, which indicates which
rows of the input data frame are selected by the brush (\code{TRUE} for
selected, \code{FALSE} for not-selected). This is enabled by setting
\code{allRows=TRUE} option. If this is used, the resulting data frame will not
be sorted by distance to the mouse event.
The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2} arguments
specify which columns in the data correspond to the x variable, y variable,
and panel variables of the plot. For example, if your plot is
\code{plot(x=cars$speed, y=cars$dist)}, and your click variable is named
\code{"cars_click"}, then you would use \code{nearPoints(cars, input$cars_brush, "speed", "dist")}.
}
\examples{
\dontrun{
# Note that in practice, these examples would need to go in reactives
# or observers.
# This would select all points within 5 pixels of the click
nearPoints(mtcars, input$plot_click)
# Select just the nearest point within 10 pixels of the click
nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
}
}
\seealso{
\code{\link[=plotOutput]{plotOutput()}} for more examples.
}

View File

@@ -12,7 +12,11 @@ imageOutput(
click = NULL,
dblclick = NULL,
hover = NULL,
hoverDelay = NULL,
hoverDelayType = NULL,
brush = NULL,
clickId = NULL,
hoverId = NULL,
inline = FALSE
)
@@ -23,7 +27,11 @@ plotOutput(
click = NULL,
dblclick = NULL,
hover = NULL,
hoverDelay = NULL,
hoverDelayType = NULL,
brush = NULL,
clickId = NULL,
hoverId = NULL,
inline = FALSE
)
}
@@ -59,6 +67,12 @@ named list with \code{x} and \code{y} elements indicating the mouse
position. To control the hover time or hover delay type, you must use
\code{\link[=hoverOpts]{hoverOpts()}}.}
\item{hoverDelay}{Deprecated; use \code{hover} instead. Also see the
\code{\link[=hoverOpts]{hoverOpts()}} function.}
\item{hoverDelayType}{Deprecated; use \code{hover} instead. Also see the
\code{\link[=hoverOpts]{hoverOpts()}} function.}
\item{brush}{Similar to the \code{click} argument, this can be \code{NULL}
(the default), a string, or an object created by the
\code{\link[=brushOpts]{brushOpts()}} function. If you use a value like
@@ -74,6 +88,12 @@ behavior, use \code{\link[=brushOpts]{brushOpts()}}. Multiple
value; brushing one image or plot will cause any other brushes with the
same \code{id} to disappear.}
\item{clickId}{Deprecated; use \code{click} instead. Also see the
\code{\link[=clickOpts]{clickOpts()}} function.}
\item{hoverId}{Deprecated; use \code{hover} instead. Also see the
\code{\link[=hoverOpts]{hoverOpts()}} function.}
\item{inline}{use an inline (\code{span()}) or block container (\code{div()})
for the output}
}

View File

@@ -1,48 +1,10 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/reexports.R
% Please edit documentation in R/cache-utils.R
\docType{import}
\name{reexports}
\alias{reexports}
\alias{key_missing}
\alias{is.key_missing}
\alias{tags}
\alias{p}
\alias{h1}
\alias{h2}
\alias{h3}
\alias{h4}
\alias{h5}
\alias{h6}
\alias{a}
\alias{br}
\alias{div}
\alias{span}
\alias{pre}
\alias{code}
\alias{img}
\alias{strong}
\alias{em}
\alias{hr}
\alias{tag}
\alias{tagList}
\alias{tagAppendAttributes}
\alias{tagHasAttribute}
\alias{tagGetAttribute}
\alias{tagAppendChild}
\alias{tagAppendChildren}
\alias{tagSetChildren}
\alias{HTML}
\alias{includeHTML}
\alias{includeText}
\alias{includeMarkdown}
\alias{includeCSS}
\alias{includeScript}
\alias{singleton}
\alias{is.singleton}
\alias{validateCssUnit}
\alias{htmlTemplate}
\alias{suppressDependencies}
\alias{withTags}
\title{Objects exported from other packages}
\keyword{internal}
\description{
@@ -51,7 +13,5 @@ below to see their documentation.
\describe{
\item{fastmap}{\code{\link[fastmap]{is.key_missing}}, \code{\link[fastmap]{key_missing}}}
\item{htmltools}{\code{\link[htmltools]{a}}, \code{\link[htmltools]{br}}, \code{\link[htmltools]{code}}, \code{\link[htmltools]{div}}, \code{\link[htmltools]{em}}, \code{\link[htmltools]{h1}}, \code{\link[htmltools]{h2}}, \code{\link[htmltools]{h3}}, \code{\link[htmltools]{h4}}, \code{\link[htmltools]{h5}}, \code{\link[htmltools]{h6}}, \code{\link[htmltools]{hr}}, \code{\link[htmltools]{HTML}}, \code{\link[htmltools]{htmlTemplate}}, \code{\link[htmltools]{img}}, \code{\link[htmltools]{includeCSS}}, \code{\link[htmltools]{includeHTML}}, \code{\link[htmltools]{includeMarkdown}}, \code{\link[htmltools]{includeScript}}, \code{\link[htmltools]{includeText}}, \code{\link[htmltools]{is.singleton}}, \code{\link[htmltools]{p}}, \code{\link[htmltools]{pre}}, \code{\link[htmltools]{singleton}}, \code{\link[htmltools]{span}}, \code{\link[htmltools]{strong}}, \code{\link[htmltools]{suppressDependencies}}, \code{\link[htmltools]{tag}}, \code{\link[htmltools]{tagAppendAttributes}}, \code{\link[htmltools]{tagAppendChild}}, \code{\link[htmltools]{tagAppendChildren}}, \code{\link[htmltools]{tagGetAttribute}}, \code{\link[htmltools]{tagHasAttribute}}, \code{\link[htmltools]{tagList}}, \code{\link[htmltools]{tags}}, \code{\link[htmltools]{tagSetChildren}}, \code{\link[htmltools]{validateCssUnit}}, \code{\link[htmltools]{withTags}}}
}}

View File

@@ -4,7 +4,7 @@
\alias{runTests}
\title{Runs the tests associated with this Shiny app}
\usage{
runTests(appDir = ".", filter = NULL, assert = TRUE, envir = globalenv())
runTests(appDir = ".", filter = NULL)
}
\arguments{
\item{appDir}{The base directory for the application.}
@@ -12,19 +12,6 @@ runTests(appDir = ".", filter = NULL, assert = TRUE, envir = globalenv())
\item{filter}{If not \code{NULL}, only tests with file names matching this regular
expression will be executed. Matching is performed on the file name
including the extension.}
\item{assert}{Logical value which determines if an error should be thrown if any error is captured.}
\item{envir}{Parent testing environment in which to base the individual testing environments.}
}
\value{
A data frame classed with the supplemental class \code{"shiny_runtests"}.
The data frame has the following columns:\tabular{lll}{
\strong{Name} \tab \strong{Type} \tab \strong{Meaning} \cr
\code{file} \tab \code{character(1)} \tab File name of the runner script in \verb{tests/} that was sourced. \cr
\code{pass} \tab \code{logical(1)} \tab Whether or not the runner script signaled an error when sourced. \cr
\code{result} \tab any or \code{NA} \tab The return value of the runner \cr
}
}
\description{
Sources the \code{.R} files in the top-level of \verb{tests/} much like \verb{R CMD check}.
@@ -33,7 +20,8 @@ directories under \verb{tests/}.
}
\details{
Historically, \href{https://rstudio.github.io/shinytest/}{shinytest}
recommended placing tests at the top-level of the \verb{tests/} directory.
This older folder structure is not supported by runTests.
Please see \code{\link[=shinyAppTemplate]{shinyAppTemplate()}} for more details.
recommended placing tests at the top-level of the \verb{tests/} directory. In
order to support that model, \code{testApp} first checks to see if the \code{.R}
files in the \verb{tests/} directory are all shinytests; if so, just calls out
to \code{\link[shinytest:testApp]{shinytest::testApp()}}.
}

View File

@@ -1,70 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/app_template.R
\name{shinyAppTemplate}
\alias{shinyAppTemplate}
\title{Generate a Shiny application from a template}
\usage{
shinyAppTemplate(path = NULL, examples = "default", dryrun = FALSE)
}
\arguments{
\item{path}{Path to create new shiny application template.}
\item{examples}{Either one of "default", "ask", "all", or any combination of
"app", "rdir", "module", "shinytest", and "testthat". In an
interactive session, "default" falls back to "ask"; in a non-interactive
session, "default" falls back to "all". With "ask", this function will
prompt the user to select which template items will be added to the new app
directory. With "all", all template items will be added to the app
directory.}
\item{dryrun}{If \code{TRUE}, don't actually write any files; just print out which
files would be written.}
}
\description{
This function populates a directory with files for a Shiny application.
}
\details{
In an interactive R session, this function will, by default, prompt the user
which components to add to the application.
The full example application includes the following files and directories:\preformatted{appdir/
|- app.R
|- R
| |- my-module.R
| `- sort.R
`- tests
|- shinytest.R
|- shinytest
| `- mytest.R
|- testthat.R
`- testthat
|- helper-load.R
|- test-mymodule.R
|- test-server.R
`- test-sort.R
}
Some notes about these files:
\itemize{
\item \code{app.R} is the main application file.
\item All files in the \verb{R/} subdirectory are automatically sourced when the
application is run.
\item \code{R/sort.R} and \code{R/my-module.R} are automatically sourced when
the application is run. The first contains a function \code{lexical_sort()},
and the second contains code for a \href{moduleServer()}{Shiny module} which
is used in the application.
\item \verb{tests/} contains various tests for the application. You may
choose to use or remove any of them. They can be executed by the
\code{\link[=runTests]{runTests()}} function.
\item \code{tests/shinytest.R} is a test runner for test files in the
\verb{tests/shinytest/} directory.
\item \code{tests/shinytest/mytest.R} is a test that uses the
\href{https://rstudio.github.io/shinytest/}{shinytest} package to do
snapshot-based testing.
\item \code{tests/testthat.R} is a test runner for test files in the
\verb{tests/testthat/} directory using the \href{https://testthat.r-lib.org/}{testthat} package.
\item \code{tests/testthat/test-mymodule.R} is a test for an application's module server function.
\item \code{tests/testthat/test-server.R} is a test for the application's server code
\item \code{tests/testthat/test-sort.R} is a test for a supporting function in the \verb{R/} directory.
}
}

20
man/singleton.Rd Normal file
View File

@@ -0,0 +1,20 @@
\name{singleton}
\alias{singleton}
\alias{is.singleton}
\title{Include content only once}
\usage{
singleton(x, value = TRUE)
is.singleton(x)
}
\arguments{
\item{x}{A \code{\link{tag}}, text, \code{\link{HTML}}, or list.}
\item{value}{Whether the object should be a singleton.}
}
\description{
Use \code{singleton} to wrap contents (tag, text, HTML, or lists) that should
be included in the generated document only once, yet may appear in the
document-generating code more than once. Only the first appearance of the
content (in document order) will be used.
}

View File

@@ -0,0 +1,21 @@
\name{suppressDependencies}
\alias{suppressDependencies}
\title{Suppress web dependencies}
\usage{
suppressDependencies(...)
}
\arguments{
\item{...}{Names of the dependencies to suppress. For example,
\code{"jquery"} or \code{"bootstrap"}.}
}
\description{
This suppresses one or more web dependencies. It is meant to be used when a
dependency (like a JavaScript or CSS file) is declared in raw HTML, in an
HTML template.
}
\seealso{
\code{\link{htmlTemplate}} for more information about using HTML
templates.
\code{\link[htmltools]{htmlDependency}}
}

View File

@@ -2,12 +2,9 @@
% Please edit documentation in R/bootstrap.R
\name{tabPanel}
\alias{tabPanel}
\alias{tabPanelBody}
\title{Create a tab panel}
\usage{
tabPanel(title, ..., value = title, icon = NULL)
tabPanelBody(..., value = NULL, icon = NULL)
}
\arguments{
\item{title}{Display title for tab}
@@ -25,16 +22,9 @@ valid when using a \code{tabPanel} within a \code{\link[=navbarPage]{navbarPage(
A tab that can be passed to \code{\link[=tabsetPanel]{tabsetPanel()}}
}
\description{
Create a tab panel
Create a tab panel that can be included within a \code{\link[=tabsetPanel]{tabsetPanel()}} or
a \code{\link[=navbarPage]{navbarPage()}}.
}
\section{Functions}{
\itemize{
\item \code{tabPanel}: Create a tab panel that can be included within a \code{\link[=tabsetPanel]{tabsetPanel()}} or a \code{\link[=navbarPage]{navbarPage()}}.
\item \code{tabPanelBody}: Create a tab panel that drops the title argument.
This function should be used within \code{tabsetPanel(type = "hidden")}. See \code{\link[=tabsetPanel]{tabsetPanel()}} for example usage.
}}
\examples{
# Show a tabset that includes a plot, summary, and
# table view of the generated distribution

View File

@@ -8,7 +8,7 @@ tabsetPanel(
...,
id = NULL,
selected = NULL,
type = c("tabs", "pills", "hidden"),
type = c("tabs", "pills"),
position = NULL
)
}
@@ -24,13 +24,8 @@ will correspond to the \code{value} argument that is passed to
of the tab that should be selected by default. If \code{NULL}, the first
tab will be selected.}
\item{type}{\describe{
\item{\code{"tabs"}}{Standard tab look}
\item{\code{"pills"}}{Selected tabs use the background fill color}
\item{\code{"hidden"}}{Hides the selectable tabs. Use \code{type = "hidden"} in
conjunction with \code{\link[=tabPanelBody]{tabPanelBody()}} and \code{\link[=updateTabsetPanel]{updateTabsetPanel()}} to control the
active tab via other input controls. (See example below)}
}}
\item{type}{Use "tabs" for the standard look; Use "pills" for a more plain
look where tabs are selected using a background fill color.}
\item{position}{This argument is deprecated; it has been discontinued in
Bootstrap 3.}
@@ -52,35 +47,6 @@ mainPanel(
tabPanel("Table", tableOutput("table"))
)
)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
radioButtons("controller", "Controller", 1:3, 1)
),
mainPanel(
tabsetPanel(
id = "hidden_tabs",
# Hide the tab values.
# Can only switch tabs by using `updateTabsetPanel()`
type = "hidden",
tabPanelBody(value = "panel1", "Panel 1 content"),
tabPanelBody(value = "panel2", "Panel 2 content"),
tabPanelBody(value = "panel3", "Panel 3 content")
)
)
)
)
server <- function(input, output, session) {
observeEvent(input$controller, {
updateTabsetPanel(session, "hidden_tabs", selected = paste0("panel", input$controller))
})
}
if (interactive()) {
shinyApp(ui, server)
}
}
\seealso{
\code{\link[=tabPanel]{tabPanel()}}, \code{\link[=updateTabsetPanel]{updateTabsetPanel()}},

82
man/tag.Rd Normal file
View File

@@ -0,0 +1,82 @@
\name{tag}
\alias{tag}
\alias{tagList}
\alias{tagAppendAttributes}
\alias{tagHasAttribute}
\alias{tagGetAttribute}
\alias{tagAppendChild}
\alias{tagAppendChildren}
\alias{tagSetChildren}
\title{HTML Tag Object}
\usage{
tagList(...)
tagAppendAttributes(tag, ...)
tagHasAttribute(tag, attr)
tagGetAttribute(tag, attr)
tagAppendChild(tag, child)
tagAppendChildren(tag, ..., list = NULL)
tagSetChildren(tag, ..., list = NULL)
tag(`_tag_name`, varArgs, .noWS = NULL)
}
\arguments{
\item{...}{Unnamed items that comprise this list of tags.}
\item{tag}{A tag to append child elements to.}
\item{attr}{The name of an attribute.}
\item{child}{A child element to append to a parent tag.}
\item{list}{An optional list of elements. Can be used with or instead of the
\code{...} items.}
\item{_tag_name}{HTML tag name}
\item{varArgs}{List of attributes and children of the element. Named list
items become attributes, and unnamed list items become children. Valid
children are tags, single-character character vectors (which become text
nodes), and raw HTML (see \code{\link{HTML}}). You can also pass lists that
contain tags, text nodes, and HTML.}
\item{.noWS}{Character vector used to omit some of the whitespace that would
normally be written around this tag. Valid options include \code{before},
\code{after}, \code{outside}, \code{after-begin}, and \code{before-end}.
Any number of these options can be specified.}
}
\value{
An HTML tag object that can be rendered as HTML using
\code{\link{as.character}()}.
}
\description{
\code{tag()} creates an HTML tag definition. Note that all of the valid HTML5
tags are already defined in the \code{\link{tags}} environment so these
functions should only be used to generate additional tags.
\code{tagAppendChild()} and \code{tagList()} are for supporting package
authors who wish to create their own sets of tags; see the contents of
bootstrap.R for examples.
}
\examples{
tagList(tags$h1("Title"),
tags$h2("Header text"),
tags$p("Text here"))
# Can also convert a regular list to a tagList (internal data structure isn't
# exactly the same, but when rendered to HTML, the output is the same).
x <- list(tags$h1("Title"),
tags$h2("Header text"),
tags$p("Text here"))
tagList(x)
# suppress the whitespace between tags
oneline <- tag("span",
tag("strong", "Super strong", .noWS="outside")
)
cat(as.character(oneline))
}

73
man/testModule.Rd Normal file
View File

@@ -0,0 +1,73 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/test-module.R
\name{testModule}
\alias{testModule}
\alias{testServer}
\title{Integration testing for Shiny modules or server functions}
\usage{
testModule(module, expr, ...)
testServer(expr, appDir = NULL)
}
\arguments{
\item{module}{The module to test}
\item{expr}{Test code containing expectations. The test expression will run
in the module's environment, meaning that the module's parameters (e.g.
\code{input}, \code{output}, and \code{session}) will be available along with any other
values created inside of the module.}
\item{...}{Additional arguments to pass to the module function. These
arguments are processed with \code{\link[rlang:list2]{rlang::list2()}} and so are
\emph{\link[rlang:dyn-dots]{dynamic}}.}
\item{appDir}{The directory root of the Shiny application. If \code{NULL}, this function
will work up the directory hierarchy --- starting with the current directory ---
looking for a directory that contains an \code{app.R} or \code{server.R} file.}
}
\value{
The result of evaluating \code{expr}.
}
\description{
Offer a way to test the reactive interactions in Shiny --- either in Shiny
modules or in the server portion of a Shiny application. For more
information, visit \href{https://shiny.rstudio.com/articles/integration-testing.html}{the Shiny Dev Center article on integration testing}.
}
\examples{
module <- function(input, output, session, multiplier = 2, prefix = "I am ") {
myreactive <- reactive({
input$x * multiplier
})
output$txt <- renderText({
paste0(prefix, myreactive())
})
}
# Basic Usage
# -----------
testModule(module, {
session$setInputs(x = 1)
# You're also free to use third-party
# testing packages like testthat:
# 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.
}, multiplier = 2)
# Advanced Usage
# --------------
multiplier_arg_name = "multiplier"
more_args <- list(prefix = "I am ")
testModule(module, {
session$setInputs(x = 1)
stopifnot(myreactive() == 2)
stopifnot(output$txt == "I am 2")
# !!/:= and !!! from rlang are used below to splice computed arguments
# into the testModule() argument list.
}, !!multiplier_arg_name := 2, !!!more_args)
}

View File

@@ -1,59 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/test-server.R
\name{testServer}
\alias{testServer}
\title{Reactive testing for Shiny server functions and modules}
\usage{
testServer(app = NULL, expr, args = list())
}
\arguments{
\item{app}{The path to an application or module to test. In addition to
paths, applications may be represented by any object suitable for coercion
to an \code{appObj} by \code{as.shiny.appobj}. Application server functions must
include a \code{session} argument in order to be tested. If \code{app} is \code{NULL} or
not supplied, the nearest enclosing directory that is a Shiny app, starting
with the current directory, is used.}
\item{expr}{Test code containing expectations. The test expression will run
in the server function environment, meaning that the parameters of the
server function (e.g. \code{input}, \code{output}, and \code{session}) will be available
along with any other values created inside of the server function.}
\item{args}{Additional arguments to pass to the module function.
If \code{app} is a module, and no \code{id} argument is provided, one will be
generated and supplied automatically.}
}
\value{
The result of evaluating \code{expr}.
}
\description{
A way to test the reactive interactions in Shiny applications. Reactive
interactions are defined in the server function of applications and in
modules.
}
\examples{
server <- function(id, multiplier = 2, prefix = "I am ") {
moduleServer(id, function(input, output, session) {
myreactive <- reactive({
input$x * multiplier
})
output$txt <- renderText({
paste0(prefix, myreactive())
})
})
}
testServer(server, args = list(multiplier = 2), {
session$setInputs(x = 1)
# You're also free to use third-party
# testing packages like testthat:
# 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

@@ -2,12 +2,9 @@
% Please edit documentation in R/update-input.R
\name{updateActionButton}
\alias{updateActionButton}
\alias{updateActionLink}
\title{Change the label or icon of an action button on the client}
\usage{
updateActionButton(session, inputId, label = NULL, icon = NULL)
updateActionLink(session, inputId, label = NULL, icon = NULL)
}
\arguments{
\item{session}{The \code{session} object passed to function given to
@@ -45,15 +42,13 @@ can be cleared by using \code{selected=character(0)}.
if (interactive()) {
ui <- fluidPage(
actionButton("update", "Update other buttons and link"),
actionButton("update", "Update other buttons"),
br(),
actionButton("goButton", "Go"),
br(),
actionButton("goButton2", "Go 2", icon = icon("area-chart")),
br(),
actionButton("goButton3", "Go 3"),
br(),
actionLink("goLink", "Go Link")
actionButton("goButton3", "Go 3")
)
server <- function(input, output, session) {
@@ -74,11 +69,6 @@ server <- function(input, output, session) {
# unchaged and changes its label
updateActionButton(session, "goButton3",
label = "New label 3")
# Updates goLink's label and icon
updateActionButton(session, "goLink",
label = "New link label",
icon = icon("link"))
})
}

38
man/validateCssUnit.Rd Normal file
View File

@@ -0,0 +1,38 @@
\name{validateCssUnit}
\alias{validateCssUnit}
\title{Validate proper CSS formatting of a unit}
\usage{
validateCssUnit(x)
}
\arguments{
\item{x}{The unit to validate. Will be treated as a number of pixels if a
unit is not specified.}
}
\value{
A properly formatted CSS unit of length, if possible. Otherwise, will
throw an error.
}
\description{
Checks that the argument is valid for use as a CSS unit of length.
}
\details{
\code{NULL} and \code{NA} are returned unchanged.
Single element numeric vectors are returned as a character vector with the
number plus a suffix of \code{"px"}.
Single element character vectors must be \code{"auto"} or \code{"inherit"},
a number, or a length calculated by the \code{"calc"} CSS function.
If the number has a suffix, it must be valid: \code{px},
\code{\%}, \code{ch}, \code{em}, \code{rem}, \code{pt}, \code{in}, \code{cm},
\code{mm}, \code{ex}, \code{pc}, \code{vh}, \code{vw}, \code{vmin}, or
\code{vmax}.
If the number has no suffix, the suffix \code{"px"} is appended.
Any other value will cause an error to be thrown.
}
\examples{
validateCssUnit("10\%")
validateCssUnit(400) #treated as '400px'
}

39
man/withTags.Rd Normal file
View File

@@ -0,0 +1,39 @@
\name{withTags}
\alias{withTags}
\title{Evaluate an expression using \code{tags}}
\usage{
withTags(code)
}
\arguments{
\item{code}{A set of tags.}
}
\description{
This function makes it simpler to write HTML-generating code. Instead of
needing to specify \code{tags} each time a tag function is used, as in
\code{tags$div()} and \code{tags$p()}, code inside \code{withTags} is
evaluated with \code{tags} searched first, so you can simply use
\code{div()} and \code{p()}.
}
\details{
If your code uses an object which happens to have the same name as an
HTML tag function, such as \code{source()} or \code{summary()}, it will call
the tag function. To call the intended (non-tags function), specify the
namespace, as in \code{base::source()} or \code{base::summary()}.
}
\examples{
# Using tags$ each time
tags$div(class = "myclass",
tags$h3("header"),
tags$p("text")
)
# Equivalent to above, but using withTags
withTags(
div(class = "myclass",
h3("header"),
p("text")
)
)
}

3
revdep/.gitignore vendored
View File

@@ -1,3 +0,0 @@
*.noindex*
data.sqlite
failures.md

View File

@@ -1,23 +0,0 @@
# Platform
|field |value |
|:--------|:----------------------------|
|version |R version 3.6.1 (2019-07-05) |
|os |macOS Catalina 10.15.3 |
|system |x86_64, darwin15.6.0 |
|ui |X11 |
|language |(EN) |
|collate |en_US.UTF-8 |
|ctype |en_US.UTF-8 |
|tz |America/New_York |
|date |2020-03-05 |
# Dependencies
|package |old |new |Δ |
|:-------|:-----|:-------|:--|
|shiny |1.4.0 |1.4.0.1 |* |
|rlang |NA |0.4.5 |* |
# Revdeps

View File

@@ -1 +0,0 @@
*Wow, no problems at all. :)*

View File

@@ -1,6 +0,0 @@
## revdepcheck results
We checked 836 reverse dependencies (719 from CRAN + 117 from BioConductor), comparing R CMD check results across CRAN and dev versions of this package.
* We saw 0 new problems
* We failed to check 0 packages

View File

@@ -1,8 +1,4 @@
library(testthat)
library(shiny)
# only test if testthat is available
if (require(testthat)) {
library(testthat)
library(shiny)
test_check("shiny")
}
test_check("shiny")

View File

@@ -1,2 +1,2 @@
source_wd <- getwd()
helper1 <- 123

View File

@@ -1,2 +1 @@
global <- "ABC"
global_wd <- getwd()

View File

@@ -1,2 +1,2 @@
runner1_A <- 1
a <- 1

View File

@@ -1,19 +1,13 @@
b <- 2
withr::with_environment(
shiny::loadSupport(),
{
runner2_B <- 2
if (!identical(helper1, 123)){
stop("Missing helper1")
}
if (!identical(helper2, "abc")){
stop("Missing helper2")
}
if (exists("runner1_A")){
stop("runner1_A exists -- are we leaking in between test environments?")
}
}
)
if (!identical(helper1, "abc")){
stop("Missing helper1")
}
if (!identical(helper2, 123)){
stop("Missing helper2")
}
if (exists("a")){
stop("a exists -- are we leaking in between test environments?")
}

View File

@@ -48,7 +48,7 @@ ui <- fluidPage(
)
# Define server logic for random distribution app ----
server <- function(input, output, session) {
server <- function(input, output) {
# Reactive expression to generate the requested distribution ----
# This is called whenever the inputs change. The output functions

View File

@@ -1,37 +0,0 @@
linkedScatterUI <- function(id) {
ns <- NS(id)
fluidRow(
column(6, plotOutput(ns("plot1"), brush = ns("brush"))),
column(6, plotOutput(ns("plot2"), brush = ns("brush")))
)
}
linkedScatterServer <- function(id, data, left, right) {
moduleServer(
id,
function(input, output, session) {
# Yields the data frame with an additional column "selected_"
# that indicates whether that observation is brushed
dataWithSelection <- reactive({
# dput(input$brush)
brushedPoints(data(), input$brush, allRows = TRUE)
})
output$plot1 <- renderPlot({
scatterPlot(dataWithSelection(), left())
})
output$plot2 <- renderPlot({
scatterPlot(dataWithSelection(), right())
})
return(dataWithSelection)
}
)
}

View File

@@ -1,7 +0,0 @@
alpha_val <- 0.2
scatterPlot <- function(data, cols) {
ggplot(data, aes_string(x = cols[1], y = cols[2])) +
geom_point(aes(color = selected_), alpha = alpha_val) +
scale_color_manual(values = c("black", "#66D65C"), guide = FALSE)
}

View File

@@ -1,24 +0,0 @@
library(shiny)
library(ggplot2)
ui <- fixedPage(
h2("Module example"),
linkedScatterUI("scatters"),
textOutput("summary")
)
server <- function(input, output, session) {
df <- linkedScatterServer(
"scatters",
reactive(mpg), # data
left = reactive(c("cty", "hwy")),
right = reactive(c("drv", "hwy"))
)
output$summary <- renderText({
sprintf("%d observation(s) selected", sum(df()$selected_))
})
}
shinyApp(ui, server)

View File

@@ -1,7 +0,0 @@
library(testthat)
test_dir(
"./testthat",
env = shiny::loadSupport(),
reporter = c("progress", "fail")
)

View File

@@ -1,38 +0,0 @@
context("linkedScatterServer")
testServer(
linkedScatterServer,
args = list(
data = reactive(ggplot2::mpg),
left = reactive(c("cty", "hwy")),
right = reactive(c("drv", "hwy"))
),
{
# Init count... 0
expect_equal(sum(dataWithSelection()$selected_), 0)
# Select a region
session$setInputs(
brush =
list(xmin = 0.84909732337501, xmax = 1.289072630224, ymin = 23.228930276968,
ymax = 29.434482709514, coords_css = list(xmin = 105.5999755859,
xmax = 176.5999755859, ymin = 172.2000007629, ymax = 236.2000007629),
coords_img = list(xmin = 263.99993896475, xmax = 441.49993896475,
ymin = 430.50000190725, ymax = 590.50000190725), img_css_ratio = list(
x = 2.5, y = 2.5), mapping = list(colour = "selected_",
x = "drv", y = "hwy"), domain = list(left = 0.4, right = 3.6,
bottom = 10.4, top = 45.6, discrete_limits = list(x = list(
"4", "f", "r"))), range = list(left = 82.8198280399793,
right = 1373.80136986301, bottom = 921.272945432678,
top = 13.6986301369863), log = list(x = NULL, y = NULL),
direction = "xy", brushId = "scatters-brush", outputId = "scatters-plot2"
)
)
# Check the value of the reactiveVal `count()`
expect_equal(sum(dataWithSelection()$selected_), 23)
}
)

View File

@@ -1,7 +0,0 @@
context("plot")
test_that("Value exists", {
expect_true(exists("alpha_val"))
expect_equal(alpha_val, 0.2)
})

View File

@@ -1,29 +0,0 @@
context("app")
testServer(expr = {
# Init count... 0
expect_equal(sum(df()$selected_), 0)
expect_equal(output$summary, "0 observation(s) selected")
# Select a region
session$setInputs(
`scatters-brush` =
list(xmin = 0.84909732337501, xmax = 1.289072630224, ymin = 23.228930276968,
ymax = 29.434482709514, coords_css = list(xmin = 105.5999755859,
xmax = 176.5999755859, ymin = 172.2000007629, ymax = 236.2000007629),
coords_img = list(xmin = 263.99993896475, xmax = 441.49993896475,
ymin = 430.50000190725, ymax = 590.50000190725), img_css_ratio = list(
x = 2.5, y = 2.5), mapping = list(colour = "selected_",
x = "drv", y = "hwy"), domain = list(left = 0.4, right = 3.6,
bottom = 10.4, top = 45.6, discrete_limits = list(x = list(
"4", "f", "r"))), range = list(left = 82.8198280399793,
right = 1373.80136986301, bottom = 921.272945432678,
top = 13.6986301369863), log = list(x = NULL, y = NULL),
direction = "xy", brushId = "scatters-brush", outputId = "scatters-plot2"
)
)
# Check the value of the reactiveVal `count()`
expect_equal(sum(df()$selected_), 23)
expect_equal(output$summary, "23 observation(s) selected")
})

View File

@@ -1,23 +0,0 @@
mymoduleUI <- function(id, label = "Counter") {
ns <- NS(id)
tagList(
actionButton(ns("button"), label = label),
verbatimTextOutput(ns("out"))
)
}
mymoduleServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
count()
})
count
}
)
}

View File

@@ -1,5 +0,0 @@
# Given a numeric vector, convert to strings, sort, and convert back to
# numeric.
lexical_sort <- function(x) {
as.numeric(sort(as.character(x)))
}

View File

@@ -1,32 +0,0 @@
library(shiny)
ui <- fluidPage(
# ======== Modules ========
# mymoduleUI is defined in R/my-module.R
mymoduleUI("mymodule1", "Click counter #1"),
mymoduleUI("mymodule2", "Click counter #2"),
# =========================
wellPanel(
sliderInput("size", "Data size", min = 5, max = 20, value = 10),
div("Lexically sorted sequence:"),
verbatimTextOutput("sequence")
)
)
server <- function(input, output, session) {
# ======== Modules ========
# mymoduleServer is defined in R/my-module.R
mymoduleServer("mymodule1")
mymoduleServer("mymodule2")
# =========================
data <- reactive({
# lexical_sort from R/utils.R
lexical_sort(seq_len(input$size))
})
output$sequence <- renderText({
paste(data(), collapse = " ")
})
}
shinyApp(ui, server)

View File

@@ -1,10 +0,0 @@
library(testthat)
# Run in the "current" environment, because shiny::runTests() is going to
# provision a new environment that's just for our test. And we'll want access to
# the supporting files that were already loaded into that env.
testthat::test_dir(
"./testthat",
env = shiny::loadSupport(),
reporter = c("summary", "fail")
)

View File

@@ -1,18 +0,0 @@
context("mymoduleServer")
testServer(mymoduleServer, {
# Set initial value of a button
session$setInputs(button = 0)
# Check the value of the reactiveVal `count()`
expect_equal(count(), 1)
# Check the value of the renderText()
expect_equal(output$out, "1")
# Simulate a click
session$setInputs(button = 1)
expect_equal(count(), 2)
expect_equal(output$out, "2")
})

View File

@@ -1,11 +0,0 @@
# Use testthat just for expectations
context("App")
testServer(expr = {
# Set the `size` slider and check the output
session$setInputs(size = 6)
expect_equal(output$sequence, "1 2 3 4 5 6")
session$setInputs(size = 12)
expect_equal(output$sequence, paste0(lexical_sort(1:12), collapse = " "))
})

View File

@@ -1,7 +0,0 @@
context("utils")
# Test the lexical_sort function from R/utils.R
test_that("Lexical sorting works", {
expect_equal(lexical_sort(c(1, 2, 3)), c(1, 2, 3))
expect_equal(lexical_sort(c(1, 2, 3, 13, 11, 21)), c(1, 11, 13, 2, 21, 3))
})

Some files were not shown because too many files have changed in this diff Show More