Skip to content

Commit 1d0c151

Browse files
authored
feat: Allow for dev packages to be dynamically loaded (#402)
1 parent 590c356 commit 1d0c151

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+1638
-359
lines changed

.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ _\.new\.png$
2020

2121
^tests/testthat/migrate-apps$
2222
^tests/testthat/apps$
23+
^tests/testthat/pkgs$
2324
^CRAN-SUBMISSION$
2425
^_dev$

.github/workflows/test-actions.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ jobs:
3535

3636
- uses: r-lib/actions/setup-r-dependencies@v2
3737
with:
38-
extra-packages:
39-
"local::."
38+
needs: |
39+
shinytest2-testing
40+
extra-packages: |
41+
local::.
4042
4143
# ## Suggested usage
4244
# - name: Test app

.lintr

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@ exclusions: list(
88
"inst/gha/audit-app.R" = list(
99
"object_usage_linter" = 81
1010
),
11-
"R/cpp11.R"
11+
"R/cpp11.R",
12+
"tests/testthat/pkgs/golem/R/run_app.R",
13+
"vignettes/use-package.Rmd",
14+
"tests/testthat/pkgs/"
1215
)

DESCRIPTION

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,22 @@ Imports:
3333
jsonlite,
3434
lifecycle,
3535
pingr,
36+
pkgload,
3637
R6 (>= 2.4.0),
3738
rlang (>= 1.0.0),
3839
rmarkdown,
3940
shiny,
4041
withr
4142
Suggests:
43+
box,
4244
deSolve,
4345
diffobj,
4446
ggplot2,
47+
golem,
4548
knitr,
4649
plotly,
4750
png,
51+
rhino,
4852
rstudioapi,
4953
shinytest (>= 1.5.1),
5054
shinyvalidate (>= 0.1.2),
@@ -55,9 +59,10 @@ Suggests:
5559
vdiffr (>= 1.0.0)
5660
LinkingTo:
5761
cpp11
58-
VignetteBuilder:
62+
VignetteBuilder:
5963
knitr
6064
Config/Needs/check: rstudio/shiny, bslib
65+
Config/Needs/shinytest2-testing: decor
6166
Config/Needs/website: pkgdown, tidyverse/tidytemplate
6267
Config/testthat/edition: 3
6368
Encoding: UTF-8

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# shinytest2 (development version)
22

3+
* Added support for local package development for the background Shiny App process in `AppDriver$new(app_dir=)`. In the background test R process, any `library(<pkg>)` or `require(<pkg>)` call will automatically execute `pkgload::load_all()` to load the local R package's source code. Installing the package before testing is no longer required! (#402).
4+
5+
* Added support for function execution for `AppDriver$new(app_dir=)`. You must run the App or document yourself. Similar to `{mirai}`, all packages must be `library()`'d or `require()`'d again as the function is being ran in the background R process (#402).
6+
37
* Fixed internal bug where `{testthat}` v3.3.0 changed expectation behavior for screenshot snapshots within `App$expect_values()` (#418).
48

59
# shinytest2 0.4.1

R/app-driver-dir.R

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,47 @@
1-
21
app_set_dir <- function(
3-
self, private,
2+
self,
3+
private,
44
app_dir
55
) {
66
ckm8_assert_app_driver(self, private)
77

8+
if (is.function(app_dir)) {
9+
# package_path <- tryCatch(pkgload::pkg_path(), error = function(e) NULL)
10+
package_name <- tryCatch(pkgload::pkg_name(), error = function(e) NULL)
11+
12+
if (
13+
!is.null(package_name) &&
14+
rlang::env_label(environment(app_dir)) ==
15+
rlang::env_label(pkgload::pkg_ns())
16+
) {
17+
# If the function is from the local package, ensure the package is loaded
18+
# Make a new function that first loads the package, then runs the original function
19+
message(
20+
"Upgrading `app_dir` function to load dev package: ",
21+
package_name
22+
)
23+
24+
app_fn <- app_dir
25+
app_dir <- function() {}
26+
rlang::fn_body(app_dir) <- rlang::expr({
27+
# Run app with dev package loaded
28+
# `library()` shimmed in app driver start
29+
library(!!package_name, character.only = TRUE)
30+
31+
.pkg_ns <- rlang::ns_env(!!package_name)
32+
.dev_pkg_run_app <- !!app_fn
33+
environment(.dev_pkg_run_app) <- .pkg_ns
34+
35+
.dev_pkg_run_app()
36+
})
37+
rlang::fn_env(app_dir) <- globalenv()
38+
}
39+
40+
# Set app function
41+
private$dir <- app_dir
42+
return()
43+
}
44+
845
app_dir <- app_dir_value(app_dir)
946

1047
if (!fs::dir_exists(app_dir)) {
@@ -16,7 +53,8 @@ app_set_dir <- function(
1653
paste0("\"", cur_dir, "\"")
1754
}
1855
app_abort(
19-
self, private,
56+
self,
57+
private,
2058
c(
2159
"`app_dir` must be an existing directory",
2260
"i" = paste0("Received: ", cur_dir)
@@ -25,25 +63,33 @@ app_set_dir <- function(
2563
}
2664

2765
shiny_app_and_rmd_abort <- function() {
28-
app_abort(self, private, c(
29-
"`app_dir` must be a directory containing:",
30-
"",
31-
"*" = "an `./app.R` Shiny application",
32-
"",
33-
" " = "or",
34-
"",
35-
"*" = "a Shiny R Markdown file",
36-
"*" = "a `./server.R` Shiny server",
37-
"",
38-
"i" = "If a Shiny R Markdown document is found, it will be the prefered document.",
39-
"i" = "`./app.R` is not compatible with Shiny R Markdown files."
40-
))
66+
app_abort(
67+
self,
68+
private,
69+
c(
70+
"`app_dir` must be a directory containing:",
71+
"",
72+
"*" = "an `./app.R` Shiny application",
73+
"",
74+
" " = "or",
75+
"",
76+
"*" = "a Shiny R Markdown file",
77+
"*" = "a `./server.R` Shiny server",
78+
"",
79+
"i" = "If a Shiny R Markdown document is found, it will be the prefered document.",
80+
"i" = "`./app.R` is not compatible with Shiny R Markdown files."
81+
)
82+
)
4183
}
4284
shiny_app_and_server_abort <- function() {
43-
app_abort(self, private, c(
44-
"`app_dir` contains both `app.R` and `server.R`. Unintented behavior may occur.",
45-
"!" = "Please either use `app.R` or `server.R`, but not both."
46-
))
85+
app_abort(
86+
self,
87+
private,
88+
c(
89+
"`app_dir` contains both `app.R` and `server.R`. Unintented behavior may occur.",
90+
"!" = "Please either use `app.R` or `server.R`, but not both."
91+
)
92+
)
4793
}
4894

4995
if (!fs::dir_exists(app_dir)) {
@@ -74,6 +120,10 @@ app_set_dir <- function(
74120

75121
app_get_dir <- function(self, private) {
76122
ckm8_assert_app_driver(self, private)
123+
if (is.function(private$dir)) {
124+
return(private$dir)
125+
}
126+
77127
as.character(private$dir)
78128
}
79129

@@ -82,6 +132,9 @@ app_dir_has_rmd <- function(self, private, app_dir = missing_arg()) {
82132
ckm8_assert_app_driver(self, private)
83133
app_dir <- self$get_dir()
84134
}
135+
if (is.function(app_dir)) {
136+
return(FALSE)
137+
}
85138
length(app_dir_rmd(app_dir = app_dir)) >= 1
86139
}
87140
app_dir_rmd <- function(self, private, app_dir = rlang::missing_arg()) {
@@ -90,7 +143,11 @@ app_dir_rmd <- function(self, private, app_dir = rlang::missing_arg()) {
90143
app_dir <- self$get_dir()
91144
}
92145
# Similar to https://github.com/rstudio/rmarkdown/issues/2236
93-
docs <- fs::dir_ls(app_dir, regexp = "^[^_].*\\.[Rrq][Mm][Dd]$", type = "file")
146+
docs <- fs::dir_ls(
147+
app_dir,
148+
regexp = "^[^_].*\\.[Rrq][Mm][Dd]$",
149+
type = "file"
150+
)
94151

95152
if (length(docs) >= 1) {
96153
docs <- Filter(docs, f = function(doc_path) {

R/app-driver-expect-values.R

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,32 @@ app_get_single_ioe <- function(
3030
)
3131
}
3232

33-
if (input_is_provided) ckm8_assert_single_string(input) else if (
34-
output_is_provided
35-
)
36-
ckm8_assert_single_string(output) else if (export_is_provided)
37-
ckm8_assert_single_string(export) else
33+
if (input_is_provided) {
34+
ckm8_assert_single_string(input)
35+
} else if (output_is_provided) {
36+
ckm8_assert_single_string(output)
37+
} else if (export_is_provided) {
38+
ckm8_assert_single_string(export)
39+
} else {
3840
app_abort(self, private, "Missing ioe type", .internal = TRUE)
41+
}
3942

4043
type <-
41-
if (input_is_provided) "input" else if (output_is_provided)
42-
"output" else if (export_is_provided) "export"
44+
if (input_is_provided) {
45+
"input"
46+
} else if (output_is_provided) {
47+
"output"
48+
} else if (export_is_provided) {
49+
"export"
50+
}
4351
name <-
44-
if (input_is_provided) input else if (output_is_provided) output else if (
45-
export_is_provided
46-
)
52+
if (input_is_provided) {
53+
input
54+
} else if (output_is_provided) {
55+
output
56+
} else if (export_is_provided) {
4757
export
58+
}
4859

4960
list(
5061
input = input,
@@ -307,8 +318,13 @@ app_get_shiny_test_url <- function(
307318
}
308319

309320
q_string <- function(group, value) {
310-
if (isTRUE(value)) paste0(group, "=1") else if (is.character(value))
311-
paste0(group, "=", paste(value, collapse = ",")) else ""
321+
if (isTRUE(value)) {
322+
paste0(group, "=1")
323+
} else if (is.character(value)) {
324+
paste0(group, "=", paste(value, collapse = ","))
325+
} else {
326+
""
327+
}
312328
}
313329
paste(
314330
private$shiny_test_url,

R/app-driver-initialize.R

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,18 @@ app_initialize_ <- function(
7878
} else {
7979
"!DEBUG starting shiny app from path"
8080
self$log_message("Starting Shiny app")
81-
app_set_dir(self, private, app_dir)
81+
if (is.function(app_dir)) {
82+
if (length(shiny_args) != 0) {
83+
app_abort(
84+
self,
85+
private,
86+
"When `app_dir=` is a function, `shiny_args=` must be empty"
87+
)
88+
}
89+
app_set_dir(self, private, app_dir)
90+
} else {
91+
app_set_dir(self, private, app_dir)
92+
}
8293

8394
app_start_shiny(
8495
self,
@@ -173,8 +184,9 @@ app_initialize_ <- function(
173184
self$get_chromote_session(),
174185
"Shiny.shinyapp.config.workerId"
175186
)$result$value
176-
if (identical(private$shiny_worker_id, ""))
187+
if (identical(private$shiny_worker_id, "")) {
177188
private$shiny_worker_id <- NA_character_
189+
}
178190

179191
private$shiny_test_url <- chromote_eval(
180192
self$get_chromote_session(),

0 commit comments

Comments
 (0)