diff --git a/NAMESPACE b/NAMESPACE
index 5ec57a978e..6b0e0f583e 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -28,6 +28,7 @@ export(trigger_results)
export(trigger_tests)
export(use_manual_app)
export(use_shinyjster)
+export(view_image_diffs)
export(view_test_images)
export(write_sysinfo)
importFrom(stats,setNames)
diff --git a/R/view-image-diffs.R b/R/view-image-diffs.R
new file mode 100644
index 0000000000..48f57b2321
--- /dev/null
+++ b/R/view-image-diffs.R
@@ -0,0 +1,151 @@
+# TODO
+# - Make method to check all images in git status?
+# * No. This requires too many permissions. Skipping for now.
+
+
+setup_gha_image_diffs <- function(
+ ...,
+ min_diff = 3,
+ repo_dir = ".",
+ # location to save images
+ out_dir = tempfile(),
+ sha = git_sha(repo_dir)
+ # # location of repo for comparisons
+ # vanilla_repo_dir = "../z-shinycoreci.nosync"
+) {
+ ellipsis::check_dots_empty()
+ repo_dir <- normalizePath(repo_dir)
+ # vanilla_repo_dir <- normalizePath(vanilla_repo_dir)
+ force(sha)
+ withr::local_dir(repo_dir)
+
+ out_dir <- "inst/img-diff-app"
+ dir.create(out_dir, recursive = TRUE, showWarnings = FALSE)
+
+
+ proc_locs <- as.list(Sys.which(c("git", "gm")))
+ if (proc_locs$git == "") stop("Please install git")
+ if (proc_locs$gm == "") stop("Please install graphicsmagick via `brew install graphicsmagick` or `apt-get install graphicsmagick`")
+
+ all_files <- system("git diff --name-only", intern = TRUE)
+ png_files <- all_files[fs::path_ext(all_files) == "png" & grepl("^inst/apps/", all_files)]
+
+ tmp_img_path <- file.path(out_dir, "tmp_original_image.png")
+ # Extract original image so that two files can be compared
+ get_original_file <- function(png_file) {
+ system(paste0("git show HEAD:", png_file, " > ", tmp_img_path))
+ tmp_img_path
+ }
+ on.exit(if (file.exists(tmp_img_path)) unlink(tmp_img_path))
+
+ message("\nFinding images...")
+ p <- progress::progress_bar$new(
+ format = "[:current/:total;:eta] :name",
+ total = length(png_files),
+ show_after = 0,
+ clear = FALSE
+ )
+ diffs <- lapply(png_files, function(png_file) {
+ p$tick(tokens = list(name = png_file))
+ # Compare images
+ shinytest2::screenshot_max_difference(png_file, get_original_file(png_file))
+ })
+ # diffs <- as.list(seq_along(png_files))
+
+ diffs <- setNames(diffs, png_files)
+ # hist(unlist(diffs))
+
+ diff_folder <- file.path(out_dir, "image_diffs")
+ if (dir.exists(diff_folder)) unlink(diff_folder, recursive = TRUE)
+ dir.create(diff_folder, showWarnings = FALSE, recursive = TRUE)
+
+ bad_pngs <- names(diffs[diffs > min_diff])
+ bad_diff_count <- unname(unlist(diffs[bad_pngs]))
+
+ message("\nDiff images")
+ p <- progress::progress_bar$new(
+ format = "[:current;:total;:eta] :name",
+ total = length(bad_pngs),
+ show_after = 0,
+ clear = FALSE
+ )
+ img_dt <-
+ lapply(bad_pngs, function(bad_png) {
+ p$tick(tokens = list(name = bad_png))
+ san_path <- fs::path_sanitize(bad_png, "_")
+ new_png <- fs::file_copy(
+ bad_png,
+ file.path(diff_folder, fs::path_ext_set(san_path, ".new.png")),
+ overwrite = TRUE
+ )
+ orig_png <- fs::file_copy(
+ get_original_file(bad_png),
+ file.path(diff_folder, fs::path_ext_set(san_path, ".old.png")),
+ overwrite = TRUE
+ )
+ diff_png <- file.path(diff_folder, san_path)
+ system(paste0(
+ "gm compare ", new_png, " ", orig_png, " -highlight-style assign -file ", diff_png
+ ))
+
+ dplyr::tibble(
+ diff_png = diff_png,
+ orig_png = orig_png,
+ new_png = new_png
+ )
+ }) %>%
+ dplyr::bind_rows()
+
+ png_dt <- dplyr::tibble(
+ file = bad_pngs,
+ diff_png = img_dt$diff_png,
+ orig_png = img_dt$orig_png,
+ new_png = img_dt$new_png,
+ diff_count = bad_diff_count
+ ) %>%
+ dplyr::mutate(
+ # inst/apps/041-dynamic-ui/tests/testthat/_snaps/linux-4.0/mytest/022.png
+ base = gsub("^inst/apps/", "", file),
+ app = gsub("/.*$", "", base),
+ snap = file.path(basename(dirname(base)), basename(base)),
+ platform_combo = basename(dirname(dirname(base))),
+ anchor = fs::path_sanitize(file, "_"),
+ ) %>%
+ dplyr::select(-base) %>%
+ tidyr::separate_wider_delim(cols = "platform_combo", names = c("platform", "Rver"), delim = "-", cols_remove = FALSE)
+
+ data_path <- file.path(out_dir, "data.json")
+ writeLines(
+ jsonlite::serializeJSON(png_dt, pretty = TRUE),
+ data_path,
+ useBytes = TRUE
+ )
+
+ data_path
+}
+
+
+
+#' @export
+view_image_diffs <- function(
+ ...,
+ run_fix_snaps = TRUE,
+ run_setup = TRUE,
+ open_viewer = TRUE,
+ repo_dir = ".",
+ sha = git_sha(repo_dir)
+) {
+ ellipsis::check_dots_empty()
+ if (run_fix_snaps) {
+ fix_snaps(ask_apps = FALSE, ask_branches = FALSE, repo_dir = repo_dir, sha = sha)
+ }
+ if (run_setup) {
+ setup_gha_image_diffs(repo_dir = repo_dir, sha = sha)
+ }
+
+ withr::local_dir(repo_dir)
+ rmarkdown::render("inst/img-diff-app/diff.Rmd")
+ if (open_viewer) {
+ system("open inst/img-diff-app/diff.html")
+ }
+}
diff --git a/R/zzz.R b/R/zzz.R
index 37650e30b8..0d9d80f6ec 100644
--- a/R/zzz.R
+++ b/R/zzz.R
@@ -1,5 +1,7 @@
+`%>%` <- NULL
.onLoad <- function(...) {
+ `%>%` <<- dplyr::`%>%`
apps_on_load()
}
diff --git a/README.md b/README.md
index 2cb36c37fc..32b8aeaa37 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,6 @@
-
## Installation
@@ -27,36 +26,36 @@ pak::pkg_install("rstudio/shinycoreci")
These GitHub packages will be installed to make sure the latest package development is working as expected:
- - [r-lib/cachem](http://github.com/r-lib/cachem)
- - [r-lib/fastmap](http://github.com/r-lib/fastmap)
- - [r-lib/later](http://github.com/r-lib/later)
- - [rstudio/bslib](http://github.com/rstudio/bslib)
- - [rstudio/bsicons](http://github.com/rstudio/bsicons)
- - [ramnathv/htmlwidgets](http://github.com/ramnathv/htmlwidgets)
- - [rstudio/crosstalk](http://github.com/rstudio/crosstalk)
- - [rstudio/gt](http://github.com/rstudio/gt)
- - [rstudio/DT](http://github.com/rstudio/DT)
- - [rstudio/dygraphs](http://github.com/rstudio/dygraphs)
- - [rstudio/flexdashboard](http://github.com/rstudio/flexdashboard)
- - [rstudio/fontawesome](http://github.com/rstudio/fontawesome)
- - [rstudio/htmltools](http://github.com/rstudio/htmltools)
- - [rstudio/httpuv](http://github.com/rstudio/httpuv)
- - [rstudio/leaflet](http://github.com/rstudio/leaflet)
- - [rstudio/pool](http://github.com/rstudio/pool)
- - [rstudio/promises](http://github.com/rstudio/promises)
- - [rstudio/reactlog](http://github.com/rstudio/reactlog)
- - [rstudio/sass](http://github.com/rstudio/sass)
- - [rstudio/shiny](http://github.com/rstudio/shiny)
- - [rstudio/shinymeta](http://github.com/rstudio/shinymeta)
- - [rstudio/shinytest](http://github.com/rstudio/shinytest)
- - [rstudio/shinytest2](http://github.com/rstudio/shinytest2)
- - [rstudio/shinythemes](http://github.com/rstudio/shinythemes)
- - [rstudio/shinyvalidate](http://github.com/rstudio/shinyvalidate)
- - [rstudio/thematic](http://github.com/rstudio/thematic)
- - [rstudio/webdriver](http://github.com/rstudio/webdriver)
- - [rstudio/websocket](http://github.com/rstudio/websocket)
- - [ropensci/plotly](http://github.com/ropensci/plotly)
- - [schloerke/shinyjster](http://github.com/schloerke/shinyjster)
+- [r-lib/cachem](http://github.com/r-lib/cachem)
+- [r-lib/fastmap](http://github.com/r-lib/fastmap)
+- [r-lib/later](http://github.com/r-lib/later)
+- [rstudio/bslib](http://github.com/rstudio/bslib)
+- [rstudio/bsicons](http://github.com/rstudio/bsicons)
+- [ramnathv/htmlwidgets](http://github.com/ramnathv/htmlwidgets)
+- [rstudio/crosstalk](http://github.com/rstudio/crosstalk)
+- [rstudio/gt](http://github.com/rstudio/gt)
+- [rstudio/DT](http://github.com/rstudio/DT)
+- [rstudio/dygraphs](http://github.com/rstudio/dygraphs)
+- [rstudio/flexdashboard](http://github.com/rstudio/flexdashboard)
+- [rstudio/fontawesome](http://github.com/rstudio/fontawesome)
+- [rstudio/htmltools](http://github.com/rstudio/htmltools)
+- [rstudio/httpuv](http://github.com/rstudio/httpuv)
+- [rstudio/leaflet](http://github.com/rstudio/leaflet)
+- [rstudio/pool](http://github.com/rstudio/pool)
+- [rstudio/promises](http://github.com/rstudio/promises)
+- [rstudio/reactlog](http://github.com/rstudio/reactlog)
+- [rstudio/sass](http://github.com/rstudio/sass)
+- [rstudio/shiny](http://github.com/rstudio/shiny)
+- [rstudio/shinymeta](http://github.com/rstudio/shinymeta)
+- [rstudio/shinytest](http://github.com/rstudio/shinytest)
+- [rstudio/shinytest2](http://github.com/rstudio/shinytest2)
+- [rstudio/shinythemes](http://github.com/rstudio/shinythemes)
+- [rstudio/shinyvalidate](http://github.com/rstudio/shinyvalidate)
+- [rstudio/thematic](http://github.com/rstudio/thematic)
+- [rstudio/webdriver](http://github.com/rstudio/webdriver)
+- [rstudio/websocket](http://github.com/rstudio/websocket)
+- [ropensci/plotly](http://github.com/ropensci/plotly)
+- [schloerke/shinyjster](http://github.com/schloerke/shinyjster)
Tools for manual and automated testing of shiny apps.
@@ -66,17 +65,17 @@ First, install the `{shinycoreci}` repo via {pak} (from instructions above). Bef
Commands used to test in different situations:
- - [RStudio IDE](https://rstudio.com/products/rstudio/download/#download) - `shinycoreci::test_in_ide()`
- - [RStudio Cloud](http://rstudio.cloud) - `shinycoreci::test_in_ide()`
- - [RStudio Server Pro](https://colorado.rstudio.com) - `shinycoreci::test_in_ide()`
- - R Terminal / R GUI - `shinycoreci::test_in_browser()`
- - (Any) Web Browser - `shinycoreci::test_in_browser()`
- - [shinyapps.io](http://shinyapps.io) - `shinycoreci::test_in_shinyappsio()`
- - [RStudio Connect](http://beta.rstudioconnect.com) - `shinycoreci::test_in_connect()`
- - SSO - `shinycoreci::test_in_sso(release = "focal")`
- \> Requires `Docker` application to be running
- - SSP - `shinycoreci::test_in_ssp(release = "centos7")`
- \> Requires `Docker` application to be running
+- [RStudio IDE](https://rstudio.com/products/rstudio/download/#download) - `shinycoreci::test_in_ide()`
+- [RStudio Cloud](http://rstudio.cloud) - `shinycoreci::test_in_ide()`
+- [RStudio Server Pro](https://colorado.rstudio.com) - `shinycoreci::test_in_ide()`
+- R Terminal / R GUI - `shinycoreci::test_in_browser()`
+- (Any) Web Browser - `shinycoreci::test_in_browser()`
+- [shinyapps.io](http://shinyapps.io) - `shinycoreci::test_in_shinyappsio()`
+- [RStudio Connect](http://beta.rstudioconnect.com) - `shinycoreci::test_in_connect()`
+- SSO - `shinycoreci::test_in_sso(release = "focal")`
+ \> Requires `Docker` application to be running
+- SSP - `shinycoreci::test_in_ssp(release = "centos7")`
+ \> Requires `Docker` application to be running
All testing functions may be run from within the IDE (except for R Terminal / R GUI).
@@ -121,10 +120,10 @@ When Windows virtual images update on GitHub Actions, the graphics device may be
When contributing a testing app, try to do the following:
- - Capture all the functionality with automated tests.
- - Also, where possible, write “light-weight” tests (that is, try and avoid **shinytest2** `$expect_screenshot()` where possible since they are prone to false positive differences and thus have a maintenance cost).
- - If the app does need manual testing, flag the testing app for manual testing with `shinycoreci::use_manual_app()`.
- - Add a description to the app’s UI that makes it clear what the app is testing for.
+- Capture all the functionality with automated tests.
+ - Also, where possible, write “light-weight” tests (that is, try and avoid **shinytest2** `$expect_screenshot()` where possible since they are prone to false positive differences and thus have a maintenance cost).
+ - If the app does need manual testing, flag the testing app for manual testing with `shinycoreci::use_manual_app()`.
+- Add a description to the app’s UI that makes it clear what the app is testing for.
Note that **shinycoreci** only supports `{testthat}` testing framework. Call `shinytest2::use_shinytest2(APP_DIR)` to use `{shinytest2}` and `{testthat}`
@@ -134,9 +133,7 @@ Note that **shinycoreci** only supports `{testthat}` testing framework. Call `sh
3. **testthat**: primarily useful in combination with `shiny::testServer()` to test server-side reactive logic of the application.
-
-
- - [See here](https://github.com/rstudio/shinycoreci-apps/blob/5691d1f4/apps/001-hello/tests/testthat/tests.R#L4) for an example.
+- [See here](https://github.com/rstudio/shinycoreci-apps/blob/5691d1f4/apps/001-hello/tests/testthat/tests.R#L4) for an example.
## Pruning old git branches
@@ -150,26 +147,26 @@ git fetch --prune
This repo contains several [GitHub Actions](https://github.com/features/actions) workflows:
- - [**Test apps:**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-test-matrix.yml) Run all automated tests (via `shiny::runTests()`). If on `main` branch, test results will be saved to `_test_results` branch.
- - [**Docker:**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-docker.yml) Create all SSO and SSP docker images. Docker images are hosted on [`rstudio/shinycoreci` via GitHub Packages](https://github.com/rstudio/shinycoreci/pkgs/container/shinycoreci).
- - [**Deploy**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-deploy.yml): Deploy all testing apps to [shinyapps.io](shinyapps.io) and [beta.rstudioconnect.com](https://beta.rstudioconnect.com)
- - [**Build results website**](https://github.com/rstudio/shinycoreci/actions/workflows/build-results.yml): Builds results for **Test apps** workflow. This workflow is called from within **Test apps**. After all tests have completed, this workflow will process all results in `_test_results` branch into static files, storing the results in `gh-pages` branch. Final website location of results:
- - [**Package checks**](https://github.com/rstudio/shinycoreci/actions/workflows/R-CMD-check.yaml): There are three main tasks that this workflow achieves:
- 1. Creates the `website` via `{pkgdown}`
- 2. Performs `routine` procedures like making sure all documentation and README.md is up to date
- 3. Performs `R CMD check` on `{shinycoreci}`, across macOS, Windows, and Ubuntu (multiple R versions).
- - [**Update app deps**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-deps.yml): Updates known dependencies of all Shiny applications in `./inst/apps`.
- - [**Trim old branches**](https://github.com/rstudio/shinycoreci/actions/workflows/trim-old-branches.yml): The current data model of **Test apps** workflow is to create many `gha-**` branches containing the changes of each test run on `main`. `gha-**` branches that have been stale for more than a week are removed.
+- [**Test apps:**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-test-matrix.yml) Run all automated tests (via `shiny::runTests()`). If on `main` branch, test results will be saved to `_test_results` branch.
+- [**Docker:**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-docker.yml) Create all SSO and SSP docker images. Docker images are hosted on [`rstudio/shinycoreci` via GitHub Packages](https://github.com/rstudio/shinycoreci/pkgs/container/shinycoreci).
+- [**Deploy**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-deploy.yml): Deploy all testing apps to [shinyapps.io](shinyapps.io) and [beta.rstudioconnect.com](https://beta.rstudioconnect.com)
+- [**Build results website**](https://github.com/rstudio/shinycoreci/actions/workflows/build-results.yml): Builds results for **Test apps** workflow. This workflow is called from within **Test apps**. After all tests have completed, this workflow will process all results in `_test_results` branch into static files, storing the results in `gh-pages` branch. Final website location of results:
+- [**Package checks**](https://github.com/rstudio/shinycoreci/actions/workflows/R-CMD-check.yaml): There are three main tasks that this workflow achieves:
+ 1. Creates the `website` via `{pkgdown}`
+ 2. Performs `routine` procedures like making sure all documentation and README.md is up to date
+ 3. Performs `R CMD check` on `{shinycoreci}`, across macOS, Windows, and Ubuntu (multiple R versions).
+- [**Update app deps**](https://github.com/rstudio/shinycoreci/actions/workflows/apps-deps.yml): Updates known dependencies of all Shiny applications in `./inst/apps`.
+- [**Trim old branches**](https://github.com/rstudio/shinycoreci/actions/workflows/trim-old-branches.yml): The current data model of **Test apps** workflow is to create many `gha-**` branches containing the changes of each test run on `main`. `gha-**` branches that have been stale for more than a week are removed.
### Trigger
There are a handful of methods that can be called to trigger the GHA actions.
- - `shinycoreci::trigger_tests()`: Trigger the **Test apps** workflow.
- - `shinycoreci::trigger_docker()`: Trigger the **Docker** workflow.
- - `shinycoreci::trigger_deploy()`: Trigger the **Deploy** workflow.
- - `shinycoreci::trigger_results()`: Trigger the **Build results website** workflow.
- - `shinycoreci::trigger(event_type=)`: Sends a custom event to the GHA workflow. For example, this can be used to trigger **Trim old branches** with `shinycoreci::trigger("trim")`.
+- `shinycoreci::trigger_tests()`: Trigger the **Test apps** workflow.
+- `shinycoreci::trigger_docker()`: Trigger the **Docker** workflow.
+- `shinycoreci::trigger_deploy()`: Trigger the **Deploy** workflow.
+- `shinycoreci::trigger_results()`: Trigger the **Build results website** workflow.
+- `shinycoreci::trigger(event_type=)`: Sends a custom event to the GHA workflow. For example, this can be used to trigger **Trim old branches** with `shinycoreci::trigger("trim")`.
A triggered workflow will run without having to push to the repo. Anyone with repo write access can call this command.
@@ -186,11 +183,11 @@ Example schedule where the workflow is run at 2am UTC Monday through Friday:
Schedule of `rstudio/shinycoreci` workflows:
- - 12am UTC, S-S: **Trim old branches**; \< 1 min
- - 12am UTC, M-F: **Update app deps**; \< 5 mins
- - 2am UTC, M-F: **Deploy apps**; \~ 2 hrs
- - 3am UTC, M-F: **Docker**; \~ 1 hr
- - 5am UTC, M-F: **Test apps** (Internally calls **Build results website**); \~ 4 hrs
+- 12am UTC, S-S: **Trim old branches**; \< 1 min
+- 12am UTC, M-F: **Update app deps**; \< 5 mins
+- 2am UTC, M-F: **Deploy apps**; ~ 2 hrs
+- 3am UTC, M-F: **Docker**; ~ 1 hr
+- 5am UTC, M-F: **Test apps** (Internally calls **Build results website**); ~ 4 hrs
### `build-results.yml`
@@ -198,29 +195,27 @@ Breakdown of what happens in the **Build results website** workflow:
On completion of `apps-test-matrix.yml`…
- - GHA will check out the latest `_test_results` branch into the local folder.
- - GHA will check out the latest `gh-pages` branch into the `./_gh-pages` folder.
- - GHA will install R and necessary package dependencies.
- - Run `./build_site.R`
- - Read the *modify times* of each file in `_test_results` and processing files
- - Compare *modify times* to *modify times* of output files
- - If any input file is newer than the output file, reprocess the documen
- - If reprocessing, render `./render-results.Rmd` given proper subset of data
- - Save output to \`./\_gh-pages/results/YEAR/MONTH/DAY/index.html
- - Update `./_gh-pages/results/index.html` to redirect to the most recent results
- - Within the `./_gh-pages` directory
- - Add any files that have been altered
- - Commit and push back any changes to the `gh-pages` website
+- GHA will check out the latest `_test_results` branch into the local folder.
+- GHA will check out the latest `gh-pages` branch into the `./_gh-pages` folder.
+- GHA will install R and necessary package dependencies.
+- Run `./build_site.R`
+ - Read the *modify times* of each file in `_test_results` and processing files
+ - Compare *modify times* to *modify times* of output files
+ - If any input file is newer than the output file, reprocess the documen
+ - If reprocessing, render `./render-results.Rmd` given proper subset of data
+ - Save output to \`./\_gh-pages/results/YEAR/MONTH/DAY/index.html
+ - Update `./_gh-pages/results/index.html` to redirect to the most recent results
+- Within the `./_gh-pages` directory
+ - Add any files that have been altered
+ - Commit and push back any changes to the `gh-pages` website
Final results are available at:
# FAQ:
- - If you run into an odd `{pak}` installation issue:
- - Run `pak::cache_clean()` to clear the cache and try your original command again
- - Installing on fresh linux? Run these commands before testing:
-
-
+- If you run into an odd `{pak}` installation issue:
+ - Run `pak::cache_clean()` to clear the cache and try your original command again
+- Installing on fresh linux? Run these commands before testing:
``` r
pkgs <- c('base64enc', 'bslib', 'Cairo', 'clipr', 'curl', 'dbplyr', 'DiagrammeR',
diff --git a/inst/img-diff-app/.gitignore b/inst/img-diff-app/.gitignore
new file mode 100644
index 0000000000..3097b7ff5f
--- /dev/null
+++ b/inst/img-diff-app/.gitignore
@@ -0,0 +1,4 @@
+image_diffs
+tmp_original_image.png
+data.json
+diff.html
diff --git a/inst/img-diff-app/app.R b/inst/img-diff-app/app.R
new file mode 100644
index 0000000000..8bf9c330f8
--- /dev/null
+++ b/inst/img-diff-app/app.R
@@ -0,0 +1,87 @@
+library(bslib)
+library(shiny)
+
+print(getwd())
+png_dt <- jsonlite::unserializeJSON(paste0(readLines("data.json"), collapse = "\n"))
+
+ui <- bslib::page_sidebar(
+ shiny::uiOutput("title"),
+ "Diff: ", shiny::verbatimTextOutput("diff_count"),
+ shiny::imageOutput("diff_image"),
+ sidebar = bslib::sidebar(
+ style="white-space: nowrap;",
+ Map(
+ seq_len(nrow(png_dt)),
+ png_dt$app,
+ png_dt$snap,
+ png_dt$platform_combo,
+ f = function(i, app, snap, platform_combo) {
+ shiny::actionLink(
+ paste0("link_", i),
+ # paste0(app, " ", snap, " on ", platform_combo)
+ paste0(app, "/", platform_combo, "/", snap)
+ )
+ }
+ )
+ )
+)
+
+server <- function(input, output, session) {
+ bad_row <- reactiveVal(1)
+
+ lapply(seq_len(nrow(png_dt)), function(i) {
+ diff_image <- png_dt$diff_file[i]
+
+ observe({
+ req(input[[paste0("link_", i)]]) # Force reactivity
+ bad_row(i)
+ })
+ })
+
+ output$title <- shiny::renderUI({
+ req(bad_row())
+ info <- as.list(png_dt[bad_row(), ])
+ shiny::tagList(
+ shiny::h1(
+ shiny::a(
+ target="_blank",
+ href=paste0("https://github.com/rstudio/shinycoreci/tree/main/inst/apps/", info$app),
+ info$app
+ )
+ ),
+ shiny::p(
+ "Snap: ",
+ shiny::a(
+ target="_blank",
+ href=paste0("https://github.com/rstudio/shinycoreci/tree/main/", info$file),
+ paste0(tail(strsplit(info$file, "/")[[1]], 3), collapse = "/")
+ )
+ ),
+ shiny::p(
+ "App: ",
+ shiny::a(
+ target="_blank",
+ href=paste0("https://testing-apps.shinyapps.io/", info$app),
+ "shinyapps.io"
+ )
+ ),
+
+ shiny::p(
+ "Diff: ",
+ shiny::code(info$diff_count)
+ ),
+ )
+ })
+
+ output$diff_image <- shiny::renderImage({
+ req(bad_row())
+ list(
+ src = file.path("image_diffs", basename(png_dt$diff_file[bad_row()])),
+ contentType = "image/png",
+ width = "100%",
+ style="border: 1px solid black;"
+ )
+ }, deleteFile = FALSE)
+}
+
+shiny::shinyApp(ui, server)
diff --git a/inst/img-diff-app/diff.Rmd b/inst/img-diff-app/diff.Rmd
new file mode 100644
index 0000000000..1ff76d4a66
--- /dev/null
+++ b/inst/img-diff-app/diff.Rmd
@@ -0,0 +1,234 @@
+---
+title: Image diffs
+output:
+ html_document:
+ self_contained: true
+ style: style.css
+---
+
+# Latest shinytest2 snapshot diffs
+
+```{css, echo = FALSE}
+/* barret */
+body .main-container {
+ max-width: 100%;
+ margin-left: 30px;
+ margin-right: 30px;
+}
+```
+
+
+```{r, include=FALSE}
+# library(bslib)
+library(shiny)
+
+png_dt <- jsonlite::unserializeJSON(paste0(readLines("data.json"), collapse = "\n"))
+png_dt <- png_dt[order(png_dt$diff_count, decreasing = TRUE), ]
+```
+
+
+```{r, echo = FALSE}
+library(gt)
+gt_dt <- png_dt
+gt_dt$diff_img_w_anchor <- Map(
+ gt_dt$anchor,
+ gt_dt$diff_png,
+ f = function(anchor, diff_png) {
+ gt::html(as.character(shiny::a(
+ href=paste0("#", anchor),
+ shiny::img(
+ src = file.path("image_diffs", basename(diff_png)),
+ height = 100
+ )
+ )))
+ }
+)
+gt_dt$app <- Map(
+ gt_dt$app,
+ gt_dt$anchor,
+ f = function(app, anchor) {
+ gt::html(as.character(shiny::a(
+ href=paste0("https://github.com/rstudio/shinycoreci/tree/main/inst/apps/", app),
+ app
+ )))
+ }
+)
+gt_dt$snap <- Map(
+ gt_dt$snap,
+ gt_dt$anchor,
+ f = function(snap, anchor) {
+ gt::html(as.character(shiny::a(
+ href=paste0("#", anchor),
+ snap
+ )))
+ }
+)
+gt_dt %>%
+ gt() %>%
+ # gt::tab_header(
+ # title = "Latest shinytest2 snapshot diffs",
+ # subtitle = "Ordered by number of diffs"
+ # ) %>%
+ gt::cols_label(
+ app = "App",
+ snap = "Snap",
+ platform_combo = "Platform",
+ diff_count = "Diff count",
+ diff_img_w_anchor = "Diff file"
+ ) %>%
+ gt::tab_spanner(
+ label = "App",
+ columns = c(app, snap, platform_combo)
+ ) %>%
+ gt::tab_spanner(
+ label = "Diff",
+ columns = c(diff_count, diff_img_w_anchor)
+ ) %>%
+ gt::tab_options(
+ table.width = "100%",
+ column_labels.font.size = 14,
+ column_labels.font.weight = "bold",
+ heading.title.font.size = 18,
+ heading.title.font.weight = "bold",
+ heading.subtitle.font.size = 16,
+ heading.subtitle.font.weight = "bold"
+ ) %>%
+ # gt::tab_style(
+ # style = gt::cell_text(weight = "bold"),
+ # locations = gt::cells_body(
+ # columns = c(app, snap, platform_combo, diff_count, diff_file)
+ # )
+ # ) %>%
+ # gt::tab_style(
+ # style = gt::cell_text(weight = "normal"),
+ # locations = gt::cells_body(
+ # columns = c(diff_count, diff_file)
+ # )
+ # ) %>%
+ # gt::tab_style(
+ # style = gt::cell_text(weight = "normal"),
+ # locations = gt::cells_body(
+ # columns = c(app, snap, platform_combo)
+ # )
+ # ) %>%
+ # gt::tab_style(
+ # style = gt::cell_text(weight = "normal"),
+ # locations = gt::cells_body(
+ # columns = c(diff_count, diff_img_w_anchor)
+ # )
+ # ) %>%
+ # text_transform(
+ # locations = cells_body(c(diff_file)),
+ # fn = function(x) {
+ # # loop over the elements of the column
+ # Map(
+ # x,
+ # f = function(.x) {
+
+ # shiny::a(
+ # href =
+ # local_image(
+ # filename = file.path("image_diffs", basename(.x)),
+ # height = 100
+ # )
+ # )
+ # }
+ # )
+ # }
+ # ) %>%
+ gt::cols_hide(
+ columns = c(anchor, platform, Rver, file, diff_png, orig_png, new_png)
+ ) %>%
+ gt::cols_move_to_end(
+ columns = c(diff_count)
+ )
+```
+
+```{r, results='asis', echo = FALSE}
+shiny::tagList(Map(
+ seq_len(nrow(png_dt)),
+ png_dt$app,
+ png_dt$anchor,
+ png_dt$file,
+ png_dt$diff_count,
+ png_dt$diff_png,
+ png_dt$orig_png,
+ png_dt$new_png,
+ f = function(i, app, anchor, file, diff_count, diff_png, orig_png, new_png) {
+ shiny::tagList(
+ shiny::h1(
+ id= anchor,
+ shiny::a(
+ target="_blank",
+ href=paste0("https://github.com/rstudio/shinycoreci/tree/main/inst/apps/", app),
+ app
+ ),
+ shiny::code("/"),
+ shiny::a(
+ target="_blank",
+ href=paste0("https://github.com/rstudio/shinycoreci/tree/main/", file),
+ paste0(tail(strsplit(file, "/")[[1]], 3), collapse = "/")
+ ),
+ shiny::HTML(" — "),
+ shiny::code(diff_count)
+ ),
+ # shiny::p(
+ # "App: ",
+ # shiny::a(
+ # target="_blank",
+ # href=paste0("https://testing-apps.shinyapps.io/", app),
+ # "shinyapps.io"
+ # )
+ # ),
+ # shiny::p(
+ # "Diff: ",
+
+ # ),
+ shiny::div(
+ onclick="change_image(this);",
+ shiny::div("diff", style="position: absolute; bottom: 3px; right: 3px; color: grey;"),
+ shiny::img(
+ src = file.path("image_diffs", basename(diff_png)),
+ width = "100%",
+ style="border: 1px solid black;"
+ )
+ ),
+ shiny::div(
+ onclick="change_image(this);",
+ shiny::div("old", style="position: absolute; bottom: 3px; right: 3px; color: grey;"),
+ shiny::img(
+ src = file.path("image_diffs", basename(orig_png)),
+ width = "100%",
+ style="border: 1px solid black;"
+ )
+ ),
+ shiny::div(
+ onclick="change_image(this);",
+ shiny::div("new", style="position: absolute; bottom: 3px; right: 3px; color: grey;"),
+ shiny::img(
+ src = file.path("image_diffs", basename(new_png)),
+ width = "100%",
+ style="border: 1px solid black;"
+ )
+ )
+ )
+ }
+),
+shiny::tags$script(shiny::HTML(
+ '
+ function change_image(img) {
+ if (img.src.endsWidth(".new.png")) {
+ img.src = img.src.replace(".new.png", ".png");
+ } else {
+ img.src = img.src.replace(".png", ".new.png");
+ }
+ if (img.src.includes("diff")) {
+ img.src = img.src.replace("diff", "snap");
+ } else {
+ img.src = img.src.replace("snap", "diff");
+ }
+ }
+ '
+) )
+)
+```