r-lib / testthat

An R 📦 to make testing 😀
https://testthat.r-lib.org
Other
868 stars 313 forks source link

Forward shiny app options from `snapshot_review` #1928

Open stefanedwards opened 4 months ago

stefanedwards commented 4 months ago

I am unit testing a package using expect_doppelganger, but doing so from a Docker container on a remote server.

Short story, something failed and testthat suggests running testthat::snapshot_review('facet/') to review changes. Alas, it doesn't quite work as expected:

Starting Shiny app for snapshot review
ℹ Use Ctrl + C to quit
/usr/bin/xdg-open: 882: www-browser: not found
/usr/bin/xdg-open: 882: links2: not found
/usr/bin/xdg-open: 882: elinks: not found
/usr/bin/xdg-open: 882: links: not found
/usr/bin/xdg-open: 882: lynx: not found
/usr/bin/xdg-open: 882: w3m: not found
xdg-open: no method available for opening 'http://127.0.0.1:6299'

Mainly, the host and port cannot be specified from snapshot_review. That would be nice.

I suggest adding an ellipsis to snapshot_review, which gets passed on to the non-exported method review_app. Here, the arguments in ... are used in either shiny::runApp, as such,

shiny::runApp(shiny::shinyApp(ui, server), quiet = TRUE, launch.browser = shiny::paneViewer(), ...)

or in shiny::shinyApp, as such:

shiny::runApp(shiny::shinyApp(ui, server, options = list(...)), quiet = TRUE, launch.browser = shiny::paneViewer())

Or even let shapshot_review accept an explicit argument for shinyApp.

Workaround for now is to overload the two functions locally:

snapshot_review <- function (files = NULL, path = "tests/testthat", ...) {
    rlang::check_installed(c("shiny", "diffviewer"), "to use snapshot_review()")
    changed <- testthat:::snapshot_meta(files, path)
    if (nrow(changed) == 0) {
        rlang::inform("No snapshots to update")
        return(invisible())
    }
    review_app(changed$name, changed$cur, changed$new, ...)
    testthat:::rstudio_tickle()
    invisible()
}

review_app <- function (name, old_path, new_path, ...) {
    stopifnot(length(name) == length(old_path), length(old_path) ==
        length(new_path))
    n <- length(name)
    case_index <- stats::setNames(seq_along(name), name)
    handled <- rep(FALSE, n)
    ui <- shiny::fluidPage(style = "margin: 0.5em", shiny::fluidRow(style = "display: flex",
        shiny::div(style = "flex: 1 1", shiny::selectInput("cases",
            NULL, case_index, width = "100%")), shiny::div(class = "btn-group",
            style = "margin-left: 1em; flex: 0 0 auto", shiny::actionButton("skip",
                "Skip"), shiny::actionButton("accept", "Accept",
                class = "btn-success"), )), shiny::fluidRow(diffviewer::visual_diff_output("diff")))
    server <- function(input, output, session) {
        i <- shiny::reactive(as.numeric(input$cases))
        output$diff <- diffviewer::visual_diff_render({
            diffviewer::visual_diff(old_path[[i()]], new_path[[i()]])
        })
        shiny::observeEvent(input$reject, {
            rlang::inform(paste0("Rejecting snapshot: '", new_path[[i()]],
                "'"))
            unlink(new_path[[i()]])
            update_cases()
        })
        shiny::observeEvent(input$accept, {
            rlang::inform(paste0("Accepting snapshot: '", old_path[[i()]],
                "'"))
            file.rename(new_path[[i()]], old_path[[i()]])
            update_cases()
        })
        shiny::observeEvent(input$skip, {
            i <- next_case()
            shiny::updateSelectInput(session, "cases", selected = i)
        })
        update_cases <- function() {
            handled[[i()]] <<- TRUE
            i <- next_case()
            shiny::updateSelectInput(session, "cases", choices = case_index[!handled],
                selected = i)
        }
        next_case <- function() {
            if (all(handled)) {
                rlang::inform("Review complete")
                shiny::stopApp()
                return()
            }
            remaining <- case_index[!handled]
            next_cases <- which(remaining > i())
            if (length(next_cases) == 0)
                remaining[[1]]
            else remaining[[next_cases[[1]]]]
        }
    }
    rlang::inform(c("Starting Shiny app for snapshot review", i = "Use Ctrl + C to quit"))
    shiny::runApp(shiny::shinyApp(ui, server), quiet = TRUE,
        launch.browser = shiny::paneViewer(), ...)
    invisible()
}

snapshot_review('facet/', port = 8001, host="0.0.0.0")