Run `testServer()` w/ `golem::get_golem_options()` - r

I have set up a R Shiny app using the golem R-package. In the app_server() I use some globally set golem options as follows:
app_server <- function(input, output, session) {
myServer(
id = "test",
global_variable = golem::get_golem_options()$global_variable,
other_variable = other_variable
)
...
}
Now the default test testServer() in test-golem-recommended.R throws an error because within myServer() it can't find the golem options or the global_variable is NULL (which also kind of makes sense):
testServer(app_server, {
# Set and test an input
session$setInputs(x = 2)
expect_equal(input$x, 2)
})
However, is it possible to call app_server() so that the golem options are loaded first?

I encountered the same issue, and have an inelegant solution that involves setting the golem_options explicitly during the unit testing phase.
First, I defined a function to set the golem_options. This can be anywhere in your package, or defined in the testthat script:
#' Set golem options for unit testing purposes
#'
#' #param golem_opts list()
#'
#' #examples
#' \dontrun{
#' my_golem_options <-
#' list(
#' myconfig = "config01"
#' )
#' set_testing_golem_options(my_golem_options)
#' }
set_testing_golem_options <-
function (golem_opts)
{
current_golem_options = getShinyOption("golem_options")
for (n in names(golem_opts)) {
current_golem_options[[n]] <- golem_opts[[n]]
}
shinyOptions(golem_options = current_golem_options)
}
Then you want to do 3 things just before the testServer action in the test-golem-recommended.R:
Set your golem_options:
set_testing_golem_options(list(global_variable = 'somevalue'))
Alternatively, you could just make a list of options without the function listed above, but the function will respect any other shinyOptions that happen to be already set.
Explicitly define the MockShinySession that you're going to use for testing:
defined_mock_shiny_session <- MockShinySession$new()
Assign the options into the mock session:
defined_mock_shiny_session$options[['golem_options']] <-
getShinyOption("golem_options")
Now the trick is to call the testServer with the explicit MockShinySession that you've just created:
testServer(
app_server,
{
# Set and test an input
session$setInputs(x = 2)
expect_equal(input$x, 2)
},
session = defined_mock_shiny_session
)
That pattern works for me. I should probably explicitly unset any golem_options after the test is complete, or ensure that they revert back to their previous state in some way. But hopefully this will get you unstuck.
Here's everything all in one code block using the example you provided:
# Define the function to set golem_options
set_testing_golem_options <-
function (golem_opts)
{
current_golem_options = getShinyOption("golem_options")
for (n in names(golem_opts)) {
current_golem_options[[n]] <- golem_opts[[n]]
}
shinyOptions(golem_options = current_golem_options)
}
# Set the golem_options that you need
set_testing_golem_options(list(global_variable = 'somevalue'))
# Create the mock session
defined_mock_shiny_session <- MockShinySession$new()
# Assign the golem_options
defined_mock_shiny_session$options[['golem_options']] <-
getShinyOption("golem_options")
# Execute the test
testServer(
app_server,
{
# Set and test an input
session$setInputs(x = 2)
expect_equal(input$x, 2)
},
session = defined_mock_shiny_session
)

Related

making conditionalPanel work with input parameter in a R shiny Golem package

i usually see the conditionalPanel work with an event happening, like a button press or a checkbox checked etc. but here, i want it to work with a parameter passed when i run the app. i am trying to show the conditional panel only if the "input" parameter is not null, but the panel appears all the time. here's the code i tried:
app_ui.R
#' The application User-Interface
#'
#' #param request Internal parameter for `{shiny}`.
#' DO NOT REMOVE.
#' #import shiny
#' #noRd
app_ui <- function(request) {
tagList(
# Leave this function for adding external resources
golem_add_external_resources(),
# List the first level UI elements here
fluidPage(
h1("testapp"),
conditionalPanel(
condition = "!is.null(r.input)",
p('there is an input ')
)
)
)
}
app_server.R
#' The application server-side
#'
#' #param input,output,session Internal parameters for {shiny}.
#' DO NOT REMOVE.
#' #import shiny
#' #noRd
app_server <- function( input, output, session ) {
# List the first level callModules here
r <- reactiveValues(query = reactiveValues())
observe({
input <- golem::get_golem_options("input")
r$input <- input
})
}
run_app.R
#' Run the Shiny Application
#'
#' #param input input
#'
#' #export
#' #importFrom shiny shinyApp
#' #importFrom golem with_golem_options
run_app <- function(
input = NULL
) {
with_golem_options(
app = shinyApp(
ui = app_ui,
server = app_server
),
golem_opts = list(input = input)
)
}
The beauty of get_golem_options("input") is that it can be uses inside your UI too :)
So I think you can do something simpler, like :
app_ui <- function(request) {
tagList(
# Leave this function for adding external resources
golem_add_external_resources(),
# List the first level UI elements here
fluidPage(
h1("testapp"),
if (!is.null(golem::get_golem_options("input")){
your_UI_here(...)
}
)
)
}

R R6Class global obj in shinyApp

In my shinyApp, i have one global var : an R6Class, when my app is finished i remove the var from the globalEnv:
#' #importFrom shiny reactive
#' #export
gui <- function (port = getOption("shiny.port"),
host = getOption("shiny.host", "127.0.0.1"),
working.directory = getwd()) {
appDir <- system.file("shiny", "gui", package = "lidRGUI")
if (appDir == "") {
stop("Could not find shiny directory. Try re-installing `lidRGUI`.", call. = FALSE)
}
catalogModele <<- CatalogModele$new()
csvPlotsModel <<- CSVPlotsModel$new()
on.exit({
rm(list = c("catalogModele", "csvPlotsModel"), pos = ".GlobalEnv")
gc()
})
shiny::runApp(appDir, display.mode = "normal")
}
#----------------------------------
# Catalog Modele Class
#----------------------------------
#' #importFrom R6 R6Class
#' #importFrom shiny reactiveValues
#' #export CatalogModele
CatalogModele <- R6Class(
public = list(
catalogs = reactiveValues(),
add_catalog = function(key,value) {
self$catalogs[[key]] <- value
},
get_catalog = function(key) {
return(self$catalogs[[key]])
},
finalize = function() {
print("Finalize has been called!")
}
)
)
The first time i launch gui() catalogModele$catalogs is empty but after the first launch catalogModele$catalogs countains all previous initilizations.
This behavior is documented in the R6 vignettes
If your R6 class contains any fields that also have reference semantics (e.g., other R6 objects, and environments), those fields should be populated in the initialize method. If the field set to the reference object directly in the class definition, that object will be shared across all instances of the R6 objects.
reactiveValues (as well as reactiveVal) have reference semantics as mentioned here. This is why the solution you posted gives the expected results while the code in your question leads to a "shared catalog" among all instances of the class.
Sorry I didn't think about this when I wrote the answer to this question of yours but I can correct it if you want.
I found a solution but i don't know why that's works. I replaced :
public = list(
catalogs = reactiveValues(),
by
public = list(
catalogs = NULL,
initialize = function() {
self$catalogs <- reactiveValues()
},

Pass objects to shiny app and launch with runApp

I am creating a package that contains a few interactive shiny apps. The purpose of these apps is to facilitate GUI exploration of in-memory objects. For example, I have an object consisting of discretized variables I would like to pass to the shiny app and then make adjustments via the GUI interface.
However, I am running into trouble when trying to access this in-memory object from the Shiny App.
Here is the relevant code:
First, I am wrapping the shinyServer function in another function. My thought here is to give the shiny server access to the passed object.
#' #export
appServer <- function(bins) {
su <- summary(bins)
shinyServer(function(input, output) {
## values that should trigger updates when changed
values <- reactiveValues(summary=su, i=1, bins=bins)
# excluded rest of body for brevity ...
}
In this function, I create a shinyApp object and pass in the ui (in another file) and the result of the appServer function defined above.
makeApp <- function(bins) {
shiny::shinyApp(
ui = appUI,
server = appServer(bins))
}
The preceding functions are called in this function that wraps the call to runApp and takes an argument from the user.
#' #export
adjust <- function(bins) {
## access data from the app?
app <- makeApp(bins)
shiny::runApp(app)
}
How can I pass an in-memory object to a shinyApp that is imported from another package?
When I execute the above code, I receive the following error:
ERROR: path[1]="C:\Users\myusername\AppData\Local\Temp\RtmpWMpvHT\widgetbinding23e8333e5298": The system cannot find the path specified
In the example below I demonstrate how you can pass an object x from the global environment or from any other environment to the shiny app and change its value. I'm not sure if this answers your question. It maybe prove useful anyway :)
library(shiny)
x <- 5
x
deparse(substitute(x)) # is going to do the trick
fun <- function(obj) {
# get the name of the passed object
object_to_change <- deparse(substitute(obj))
# get the object from a given environment
val <- get(object_to_change, envir = .GlobalEnv)
# ?environment
# Save the object as a reactive value
values <- reactiveValues(x = val)
# Now define the app that is going to change the value of x
ui <- shinyUI(fluidPage(
br(),
actionButton("quit", "Apply changes and quit"),
textInput("new", "", value = NULL, placeholder = "Set new value of x"),
textOutput("out")
))
server <- function(input, output) {
output$out <- renderPrint({
values$x
})
# change the value of x
observe({
req(input$new)
values$x <- as.numeric(input$new)
})
# Apply changes and quit
observe({
if (input$quit == 1) {
assign(x = object_to_change, value = values$x, envir = .GlobalEnv)
stopApp()
}
})
}
# Run the app
shiny::shinyApp(ui, server)
}
fun(x)
# Check the new value of x in the .GlobalEnv
x

Shiny - Why does Shiny not react to the result of an evaluated/parsed expression?

Please refer to this question for some of the functions and context.
The Code
Right now, I'm trying to "cure" this server code:
env = module_env$normalization
eval(envir= module_env$normalization, {
jjjj <- reactive({
eval(parse(text=paste(sep='', "input$", mod_id('var_echo'))))
})
####
#### vvvvvvvvvvvvvvvvvvvv
####
observeEvent(jjjj, {
# observeEvent(evalq(parse(text=paste(sep='', 'input$', mod_id('var_echo', env)))), {
print("HEY")
print(get('sum_exp', env))
})
####
#### ^^^^^^^^^^^^^^^^^^^^^^
####
observeEvent(input$print_mod_id, {
browser()
print(mod_id('var_echo', env))
print(paste("input$", mod_id('var_echo', env), sep=''))
print(parse(text=paste(sep='', "input$", mod_id('var_echo', env))))
print(eval(parse(text=paste(sep='', "input$", mod_id('var_echo', env)))))
})
})
which is meant to be paired with this UI code:
module_env$normalization <- new.env()
env = module_env$normalization
evalq(envir = env, {
#Create the UI here
ui_normalization <-
tabsetPanel(
tabPanel('Normalization (FPKM)',
p("normalization"),
actionButton(inputId = mod_id('var_echo', env), "Echo variables"),
actionButton("print_mod_id", "Print module UI")
)
)
print(mod_id('var_echo', env))
#Create your initial input/output variables here
sum_exp <- NULL
output_sum_exp <- NULL
#Always register last
register_module(
name="Normalization (FPKM)",
module=ui_normalization,
env=env,
inputs=c('sum_exp'),
outputs=c('output_sum_exp')
)
})
These two files will be called at separate times during the app, but ultimately, will be called using source("FILENAME.R", local=TRUE) to localize the environment of the source file to the global environment, which also holds the app.
The Problem
Most of this is for debugging, but the most important thing is that the expression jjjj is being read by Shiny (the app does run), but doesn't react to when the button is pressed.
mod_id() is a special function designed to react to the environment that it is called from to generate a special key based on a string and its calling environment:
###Sample results for the print_mod_id print statements
[1] "id0x000000001e9047f0__var_echo"
[1] "input$id0x000000001e9047f0__var_echo"
expression(input$id0x000000001e9047f0__var_echo)
[1] 2
attr(,"class")
[1] "integer" "shinyActionButtonValue"
Here's mod_id(string, environment), which returns a string:
mod_id <- function(name, envir = NULL) {
if(identical(envir, globalenv())) {
e = envir
} else if (is.null(envir)) {
e = parent.frame()
} else if (is.environment(envir)) {
e = envir
} else {
stop("given non-default argument \'envir\' is not an environment")
}
return(paste(sep = "",
"id",
sub('<environment: (.*)>', '\\1', capture.output(e)),
"__",
name
)
)
}
Testing Observations
Here's some of the tests I've done:
The unique IDs generated by mod_id() for the UI and server are indeed the same. I've already gone in and fixed any discrepancies, so they do match up.
The expression used to get the final input$INPUT_ID_FOR_SHINY name works in another case, except when the variable name is substituted with the function mod_id(). I'm not exactly sure why, but it might have to do with the evaluation order caused by the overarching eval statement.
The event triggered by jjjj is triggered once at the beginning of the app. I have no idea why this happens, but the fact is that it does.
Edit: Additional testing, now have the Shiny source code on my machine. The expression is now: evalq(as.name(paste(sep='', 'input$', mod_id('var_echo', env)))) but it still doesn't work.

In Shiny apps for R, how do I delay the firing of a reactive?

I have a selectizeInput in my Shiny app. It is in multiple-select mode, so the user can specify more than one selection.
However, the reactives that depend on the selectizeInput get fired every time a selection is added. Suppose that the user intends to select A, B and C. Currently, my app will do it expensive computations for the selections A, A, B and A, B, C, when only the last is required.
The best way I can think to solve this is to delay the firing of the selectizeInput by a second or so to give the user a chance to enter all of the selections. Each new selection should set the timer back to 1 second. I know that Shiny provides an invalidateLater command, but this causes the reactive to fire once now and once later.
How can I get the reactive to only fire once later?
You should debounce the reactive.
There is an R implementation here:
https://gist.github.com/jcheng5/6141ea7066e62cafb31c
# Returns a reactive that debounces the given expression by the given time in
# milliseconds.
#
# This is not a true debounce in that it will not prevent \code{expr} from being
# called many times (in fact it may be called more times than usual), but
# rather, the reactive invalidation signal that is produced by expr is debounced
# instead. This means that this function should be used when \code{expr} is
# cheap but the things it will trigger (outputs and reactives that use
# \code{expr}) are expensive.
debounce <- function(expr, millis, env = parent.frame(), quoted = FALSE,
domain = getDefaultReactiveDomain()) {
force(millis)
f <- exprToFunction(expr, env, quoted)
label <- sprintf("debounce(%s)", paste(deparse(body(f)), collapse = "\n"))
v <- reactiveValues(
trigger = NULL,
when = NULL # the deadline for the timer to fire; NULL if not scheduled
)
# Responsible for tracking when f() changes.
observeEvent(f(), {
# The value changed. Start or reset the timer.
v$when <- Sys.time() + millis/1000
}, ignoreNULL = FALSE)
# This observer is the timer. It rests until v$when elapses, then touches
# v$trigger.
observe({
if (is.null(v$when))
return()
now <- Sys.time()
if (now >= v$when) {
v$trigger <- runif(1)
v$when <- NULL
} else {
invalidateLater((v$when - now) * 1000, domain)
}
})
# This is the actual reactive that is returned to the user. It returns the
# value of f(), but only invalidates/updates when v$trigger is touched.
eventReactive(v$trigger, {
f()
}, ignoreNULL = FALSE)
}
#' #examples
#' library(shiny)
#'
#' ui <- fluidPage(
#' numericInput("val", "Change this rapidly, then pause", 5),
#' textOutput("out")
#' )
#'
#' server <- function(input, output, session) {
#' debounced <- debounce(input$val, 1000)
#' output$out <- renderText(
#' debounced()
#' )
#' }
#'
#' shinyApp(ui, server)

Resources