INWTlab / shinyMatrix

https://inwtlab.github.io/shinyMatrix/
Other
19 stars 6 forks source link

recordTest does not work with matrixInput #12

Closed simenlonsethagen closed 4 years ago

simenlonsethagen commented 4 years ago

recordTest does not work with shinyMatrix

Not sure if this belongs here or at shinytest. Cross-posted as issue #311.

I've run in to a problem when I try to use shinytest to test an app where I use shinyMatrix's matrixInput. To reproduce, I've taken the following example app from the GitHub repo:

ui.R:

library(shinyMatrix)

m <- diag(5)
# colnames(m) <- 1:3
# rownames(m) <- letters[1:3]

shiny::tagList(
  shiny::fluidPage(
    shiny::titlePanel("Demonstration Matrix Input Field"),
    shiny::fluidRow(
      column(6, matrixInput(
        inputId = "matrix",
        value = m,
        class = "numeric",
        cols = list(
          names = TRUE
        ),
        rows = list(
          names = TRUE
        )
      )),
      column(6, tableOutput("table"))
    )
  )
)

and server.R:

library(shinyMatrix)

function(input, output, session) {
  output$table <- renderTable(input$matrix, rownames = TRUE)
}

I then record a test where I just change a number in the input matrix:

> library(shinytest)
> recordTest()

This gives the following console output:

Loading required package: shiny

Listening on http://127.0.0.1:5017
C> Running application in test mode.
C> Loading required package: shiny
C> 
C> Listening on http://127.0.0.1:7857
Saved test code to /home/simen/repos/shinymatrixshinytestissue/tests/mytest.R
Running mytest.R Error in session_makeRequest(self, private, endpoint, data, params, headers) : 
  undefined is not an object (evaluating 'value.data.length')

The recorded snapshot looks like this:

001

When I try to run the test, I get error message at the end again:

> testApp()
Running mytest.R Error in session_makeRequest(self, private, endpoint, data, params, headers) : 
  undefined is not an object (evaluating 'value.data.length')

I've tried to run it in debug mode, but I don't understand what happens in the JavaScript at line 12 in the traceback from the call above:

> traceback()
17: stop(create_condition(response, "error", call = call))
16: report_error(response)
15: session_makeRequest(self, private, endpoint, data, params, headers)
14: private$makeRequest("EXECUTE ASYNC SCRIPT", list(script = script, 
        args = args))
13: session_executeScriptAsync(self, private, script, ...)
12: private$web$executeScriptAsync("var wait = arguments[0];\n    var timeout = arguments[1];\n    var callback = arguments[2];\n    shinytest.outputValuesWaiter.start(timeout);\n    shinytest.inputQueue.flush();\n    shinytest.outputValuesWaiter.finish(wait, callback);", 
        wait, timeout)
11: sd_flushInputs(self, private, wait, timeout)
10: private$flushInputs(wait_, timeout_)
9: sd_setInputs(self, private, ..., wait_ = wait_, values_ = values_, 
       timeout_ = timeout_, allowInputNoBinding_ = allowInputNoBinding_, 
       priority_ = priority_)
8: app$setInputs(matrix = c(c("1", "0", "0", "0", "0"), c("0", "1", 
       "0", "0", "0"), c("0", "0", "1", "0", "0"), c("0", "0", "0", 
       "1", "0"), c("0", "0", "0", "1", "1"), "", "", "", "", "", 
       "", "", "", "", "")) at mytest.R#5
7: eval(ei, envir)
6: eval(ei, envir)
5: withVisible(eval(ei, envir))
4: source(testname, local = env)
3: FUN(X[[i]], ...)
2: lapply(found_testnames, function(testname) {
       withr::local_dir(testsDir)
       withr::local_envvar(c(RSTUDIO = ""))
       withr::local_options(list(shinytest.app.dir = "appdir"))
       gc()
       env <- new.env(parent = .GlobalEnv)
       if (!quiet) {
           message(testname, " ", appendLF = FALSE)
       }
       source(testname, local = env)
   })
1: testApp()

My guess is that there is something strange going on in the app$setInput call on line 8, so maybe the input to the matrix was not parsed correctly in recordTest? Here is the generated mytest.R where the app$setInput happens:

app <- ShinyDriver$new("../")
app$snapshotInit("mytest")

app$snapshot()
app$setInputs(matrix = c(c("1", "0", "0", "0", "0"), c("0", "1", "0", "0", "0"), c("0", "0", "1", "0", "0"), c("0", "0", "0", "1", "0"), c("0", "0", "0", "1", "1"), "", "", "", "", "", "", "", "", "", ""))
app$snapshot()
aneudecker commented 4 years ago

Hi @simenlonsethagen, thanks for reporting this.

The code in line 8 seems strange indeed. Did you try it with a correct matrix specification, i.e. app$setInputs(matrix = diag(5)) to make sure that the problem lies in the recording?

simenlonsethagen commented 4 years ago

Yes, I get the same error message and traceback (except in line 8):

> testApp()
Running mytest.R Error in session_makeRequest(self, private, endpoint, data, params, headers) : 
  undefined is not an object (evaluating 'value.data.length')
> traceback()
17: stop(create_condition(response, "error", call = call))
16: report_error(response)
15: session_makeRequest(self, private, endpoint, data, params, headers)
14: private$makeRequest("EXECUTE ASYNC SCRIPT", list(script = script, 
        args = args))
13: session_executeScriptAsync(self, private, script, ...)
12: private$web$executeScriptAsync("var wait = arguments[0];\n    var timeout = arguments[1];\n    var callback = arguments[2];\n    shinytest.outputValuesWaiter.start(timeout);\n    shinytest.inputQueue.flush();\n    shinytest.outputValuesWaiter.finish(wait, callback);", 
        wait, timeout)
11: sd_flushInputs(self, private, wait, timeout)
10: private$flushInputs(wait_, timeout_)
9: sd_setInputs(self, private, ..., wait_ = wait_, values_ = values_, 
       timeout_ = timeout_, allowInputNoBinding_ = allowInputNoBinding_, 
       priority_ = priority_)
8: app$setInputs(matrix = diag(5)) at mytest.R#5
7: eval(ei, envir)
6: eval(ei, envir)
5: withVisible(eval(ei, envir))
4: source(testname, local = env)
3: FUN(X[[i]], ...)
2: lapply(found_testnames, function(testname) {
       withr::local_dir(testsDir)
       withr::local_envvar(c(RSTUDIO = ""))
       withr::local_options(list(shinytest.app.dir = "appdir"))
       gc()
       env <- new.env(parent = .GlobalEnv)
       if (!quiet) {
           message(testname, " ", appendLF = FALSE)
       }
       source(testname, local = env)
   })
1: testApp()
aneudecker commented 4 years ago

I could reproduce the issue and found a quick fix in the test script. We need to pass a different object to the app$setInputs function:

app$setInputs(matrix = list(
    data = diag(5),
    rownames = 1:5,
    colnames = 1:5
  )
)

It was implemented this way as there is no canonical way to represent a matrix with optional row and column names as json object. This way we only pass simple objects.

There seems to be an issue that this complexity is not captured by the shinytest function.

simenlonsethagen commented 4 years ago

That works for me as well. Thanks.

aneudecker commented 4 years ago

Potentially an issue in shinytest. We can reopen if something comes up in https://github.com/rstudio/shinytest/issues/311