From 70120ab43566bfa1bafe3d3b57e498c3c50a0a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:20:48 +0000 Subject: [PATCH 1/6] feat: use newer expect_visible and expect_hidden implementation --- tests/testthat/helpers-shinytest2.R | 64 ++++++++++++++++--- ...test-shinytest2_DataframeFilteredDataset.R | 18 +++--- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/tests/testthat/helpers-shinytest2.R b/tests/testthat/helpers-shinytest2.R index 7ecbf739e..1bfac2af6 100644 --- a/tests/testthat/helpers-shinytest2.R +++ b/tests/testthat/helpers-shinytest2.R @@ -1,15 +1,63 @@ default_idle_timeout <- 20000 # Wait (ms) at most until idle default_idle_duration <- 200 # Time (ms) it is idle -# Check visibility (borrowed from teal.widgets/tests/testthat/helpers-utils.R) -is_visible <- function(app_driver, element) { - js_script <- sprintf(" - Array.from(document.querySelectorAll('%s')).map(el => { - return el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0); - }); - ", element) +#' Function to check if an selector is visible in a shiny app +#' +#' The [shinytest2::AppDriver$wait_for_js()] method is used to check if the selector +#' throws an error when the selector is not visible. +#' +#' @param selector `character(1)` CSS selector of the element to check visibility for. +#' @param app_driver `shinytest2::AppDriver` AppDriver object of +#' the shiny app. +#' @param timeout `numeric(1)` maximum time to wait for the element to be +#' visible. The default is the timeout set in the [shinytest2::AppDriver] object. +#' @param expectation_fun `function` expectation function to use for checking +#' visibility. +#' @return `logical(1)` whether the selector is visible. +#' @keywords internal +expect_visible <- function(selector, app_driver, timeout) { + checkmate::assert( + .var.name = "selector", + combine = "and", + checkmate::check_string(selector), + if (grepl("[\"]", selector)) "Cannot contain double quotes (\") in CSS selectors" else TRUE + ) + checkmate::assert_r6(app_driver, "AppDriver") - any(unlist(app_driver$get_js(js_script))) + tryCatch( + { + app_driver$wait_for_js( + sprintf( + "Array.from(document.querySelectorAll(\"%s\")).map(el => el.checkVisibility()).some(Boolean)", + selector + ), + timeout + ) + testthat::pass() + }, + error = function(err) { + testthat::fail(sprintf("CSS selector '%s' does not produce any visible elements.", selector)) + } + ) +} + +#' @describeIn expect_visible Check if an selector is hidden for a given timeout. +expect_hidden <- function(selector, app_driver, timeout) { + checkmate::assert_string(selector) + checkmate::assert_r6(app_driver, "AppDriver") + tryCatch( + { + app_driver$wait_for_js( + sprintf( + "!Array.from(document.querySelectorAll('%s')).map(el => el.checkVisibility()).some(Boolean)", + selector + ), + timeout + ) + testthat::pass() + }, + error = function(err) testthat::fail(sprintf("CSS selector '%s' produces visible elements.", selector)) + ) } # Write a js code to extract the classes diff --git a/tests/testthat/test-shinytest2_DataframeFilteredDataset.R b/tests/testthat/test-shinytest2_DataframeFilteredDataset.R index cdd3ce60f..83f525a3d 100644 --- a/tests/testthat/test-shinytest2_DataframeFilteredDataset.R +++ b/tests/testthat/test-shinytest2_DataframeFilteredDataset.R @@ -69,17 +69,17 @@ local_app_driver <- function(..., testthat::describe("Toggle button shows and hide", { it("'Active Filter Summary' panel", { app_driver <- local_app_driver() - testthat::expect_true(is_visible(app_driver, "#filter_panel-overview-table")) + expect_visible("#filter_panel-overview-table", app_driver) app_driver$click(selector = "#filter_panel-overview-main_filter_accordion * button") app_driver$wait_for_idle(timeout = default_idle_timeout * 8) - testthat::expect_false(is_visible(app_driver, "#filter_panel-overview-table")) + expect_hidden("#filter_panel-overview-table", app_driver) }) it("'Filter Data' panel", { app_driver <- local_app_driver() - testthat::expect_true(is_visible(app_driver, "#filter_panel-active-filter_active_vars_contents")) + expect_visible("#filter_panel-active-filter_active_vars_contents", app_driver) app_driver$click(selector = "#filter_panel-active-main_filter_accordion > div > div.accordion-header > button") app_driver$wait_for_idle(timeout = default_idle_timeout * 8) - testthat::expect_false(is_visible(app_driver, "#filter_panel-active-filter_active_vars_contents")) + expect_hidden("#filter_panel-active-filter_active_vars_contents", app_driver) }) }) @@ -129,7 +129,7 @@ testthat::test_that("Clicking add button on the datasets shows add filter panel" app_driver$click(selector = "#filter_panel-active-mtcars-add_filter_icon") app_driver$wait_for_idle(duration = default_idle_duration * 8) # Wait for the panel open animation testthat::expect_true(is_existing(app_driver, "#filter_panel-active-mtcars-mtcars-filter-var_to_add > option")) - testthat::expect_true(is_visible(app_driver, "#filter_panel-active-mtcars-mtcars-filter-var_to_add > option")) + expect_visible("#filter_panel-active-mtcars-mtcars-filter-var_to_add > option", app_driver) }) testthat::test_that("Clicking add and selecting a variable adds the card for a given variable", { @@ -187,7 +187,7 @@ testthat::test_that("Remove filter button removes a specific filter card", { teal_slice(dataname = "mtcars", varname = "mpg", selected = c(20.0, 25.0)) ) selector <- "#filter_panel-active-mtcars-filter-mtcars_mpg-remove" - testthat::expect_true(is_visible(app_driver, selector)) + expect_visible(selector, app_driver) filters_before <- app_driver$get_text("div.filter-card-varname > strong") app_driver$click(selector = selector) app_driver$wait_for_idle(default_idle_duration * 8) @@ -204,7 +204,7 @@ testthat::test_that("Remove datasets filters removes all cards.", { ) selector <- "#filter_panel-active-mtcars-remove_filters" - testthat::expect_true(is_visible(app_driver, selector)) + expect_visible(selector, app_driver) filters_before <- app_driver$get_text("div.filter-card-varname > strong") app_driver$click(selector = selector) app_driver$wait_for_idle(default_idle_duration * 8) @@ -219,7 +219,7 @@ testthat::test_that("Remove all filters button removes all cards for all dataset teal_slice(dataname = "mtcars", varname = "mpg", selected = c(20.0, 25.0)) ) selector <- "#filter_panel-active-remove_all_filters" - testthat::expect_true(is_visible(app_driver, selector)) + expect_visible(selector, app_driver) app_driver$click(selector = selector) app_driver$wait_for_idle(default_idle_duration * 8) filters_after <- app_driver$get_text("div.filter-card-varname > strong") @@ -241,5 +241,5 @@ testthat::test_that("Expanding a card shows filter choices.", { app_driver$click(selector = select_mpg) app_driver$wait_for_idle(default_idle_duration * 8) testthat::expect_true(is_existing(app_driver, "#filter_panel-active-mtcars-filter-mtcars_mpg-body")) - testthat::expect_true(is_visible(app_driver, "#filter_panel-active-mtcars-filter-mtcars_mpg-body")) + expect_visible("#filter_panel-active-mtcars-filter-mtcars_mpg-body", app_driver) }) From b6fd767618d8ac14403ee3a9dee8dceeabcc3640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:04:56 +0000 Subject: [PATCH 2/6] chore: use older api to prevent version bumping --- tests/testthat/helpers-shinytest2.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/helpers-shinytest2.R b/tests/testthat/helpers-shinytest2.R index 1bfac2af6..db21d6235 100644 --- a/tests/testthat/helpers-shinytest2.R +++ b/tests/testthat/helpers-shinytest2.R @@ -33,7 +33,7 @@ expect_visible <- function(selector, app_driver, timeout) { ), timeout ) - testthat::pass() + testthat::succeed() }, error = function(err) { testthat::fail(sprintf("CSS selector '%s' does not produce any visible elements.", selector)) @@ -54,7 +54,7 @@ expect_hidden <- function(selector, app_driver, timeout) { ), timeout ) - testthat::pass() + testthat::succeed() }, error = function(err) testthat::fail(sprintf("CSS selector '%s' produces visible elements.", selector)) ) From 5ae3f1252fb57ca0e34deaf6bca10462b5a4596b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:47:56 +0000 Subject: [PATCH 3/6] fix: keep full comparison --- tests/testthat/helpers-shinytest2.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/helpers-shinytest2.R b/tests/testthat/helpers-shinytest2.R index db21d6235..8affdb0fc 100644 --- a/tests/testthat/helpers-shinytest2.R +++ b/tests/testthat/helpers-shinytest2.R @@ -49,7 +49,7 @@ expect_hidden <- function(selector, app_driver, timeout) { { app_driver$wait_for_js( sprintf( - "!Array.from(document.querySelectorAll('%s')).map(el => el.checkVisibility()).some(Boolean)", + "!Array.from(document.querySelectorAll('%s')).map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0)).some(Boolean)", selector ), timeout From b5f6bca724ed1827b870da9c36fb73b19fdd3201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:48:53 +0000 Subject: [PATCH 4/6] fix: keep full comparison --- tests/testthat/helpers-shinytest2.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/helpers-shinytest2.R b/tests/testthat/helpers-shinytest2.R index 8affdb0fc..c9699d58d 100644 --- a/tests/testthat/helpers-shinytest2.R +++ b/tests/testthat/helpers-shinytest2.R @@ -28,7 +28,7 @@ expect_visible <- function(selector, app_driver, timeout) { { app_driver$wait_for_js( sprintf( - "Array.from(document.querySelectorAll(\"%s\")).map(el => el.checkVisibility()).some(Boolean)", + "Array.from(document.querySelectorAll(\"%s\")).map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0)).some(Boolean)", selector ), timeout From ad18160e17e633594d80dabfd4c6c5b67c128c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 19 Jan 2026 14:39:29 +0000 Subject: [PATCH 5/6] fix: feedback from @m7pr --- tests/testthat/helpers-shinytest2.R | 35 ++++++++++++------- ...test-shinytest2_DataframeFilteredDataset.R | 8 ++--- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/tests/testthat/helpers-shinytest2.R b/tests/testthat/helpers-shinytest2.R index c9699d58d..79f49c2d8 100644 --- a/tests/testthat/helpers-shinytest2.R +++ b/tests/testthat/helpers-shinytest2.R @@ -16,19 +16,15 @@ default_idle_duration <- 200 # Time (ms) it is idle #' @return `logical(1)` whether the selector is visible. #' @keywords internal expect_visible <- function(selector, app_driver, timeout) { - checkmate::assert( - .var.name = "selector", - combine = "and", - checkmate::check_string(selector), - if (grepl("[\"]", selector)) "Cannot contain double quotes (\") in CSS selectors" else TRUE - ) + checkmate::assert_string(selector) + selector <- jsonlite::toJSON(selector, auto_unbox = TRUE) checkmate::assert_r6(app_driver, "AppDriver") tryCatch( { app_driver$wait_for_js( sprintf( - "Array.from(document.querySelectorAll(\"%s\")).map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0)).some(Boolean)", + "Array.from(document.querySelectorAll(%s)).map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0)).some(Boolean)", selector ), timeout @@ -44,31 +40,44 @@ expect_visible <- function(selector, app_driver, timeout) { #' @describeIn expect_visible Check if an selector is hidden for a given timeout. expect_hidden <- function(selector, app_driver, timeout) { checkmate::assert_string(selector) + selector <- jsonlite::toJSON(selector, auto_unbox = TRUE) checkmate::assert_r6(app_driver, "AppDriver") tryCatch( { app_driver$wait_for_js( sprintf( - "!Array.from(document.querySelectorAll('%s')).map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0)).some(Boolean)", + "!Array.from(document.querySelectorAll(%s)).map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0)).some(Boolean)", selector ), timeout ) testthat::succeed() }, - error = function(err) testthat::fail(sprintf("CSS selector '%s' produces visible elements.", selector)) + error = function(err) { + testthat::fail(sprintf("CSS selector '%s' produces visible elements.", selector)) + } ) } # Write a js code to extract the classes get_attribute <- function(selector, attribute) { + checkmate::assert_string(selector) + checkmate::assert_string(attribute) + selector <- jsonlite::toJSON(selector, auto_unbox = TRUE) sprintf( - "Array.from(document.querySelectorAll('%s')).map(el => el.getAttribute('%s'))", + "Array.from(document.querySelectorAll(%s)).map(el => el.getAttribute(\"%s\"))", selector, attribute ) } -is_existing <- function(app_driver, element) { - js_script <- sprintf("document.querySelectorAll('%s').length > 0;", element) - app_driver$get_js(js_script) +expect_existing <- function(app_driver, selector) { + checkmate::assert_string(selector) + selector <- jsonlite::toJSON(selector, auto_unbox = TRUE) + app_driver$wait_for_js(sprintf("document.querySelectorAll(%s).length > 0", selector)) +} + +expect_not_existing <- function(app_driver, selector) { + checkmate::assert_string(selector) + selector <- jsonlite::toJSON(selector, auto_unbox = TRUE) + app_driver$wait_for_js(sprintf("document.querySelectorAll(%s).length == 0", selector)) } diff --git a/tests/testthat/test-shinytest2_DataframeFilteredDataset.R b/tests/testthat/test-shinytest2_DataframeFilteredDataset.R index 83f525a3d..ad5be63f8 100644 --- a/tests/testthat/test-shinytest2_DataframeFilteredDataset.R +++ b/tests/testthat/test-shinytest2_DataframeFilteredDataset.R @@ -125,10 +125,10 @@ testthat::describe("teal_slice objects pass to filter data", { testthat::test_that("Clicking add button on the datasets shows add filter panel", { app_driver <- local_app_driver() - testthat::expect_true(is_existing(app_driver, "#filter_panel-active-mtcars-add_filter_icon")) + expect_existing(app_driver, "#filter_panel-active-mtcars-add_filter_icon") app_driver$click(selector = "#filter_panel-active-mtcars-add_filter_icon") app_driver$wait_for_idle(duration = default_idle_duration * 8) # Wait for the panel open animation - testthat::expect_true(is_existing(app_driver, "#filter_panel-active-mtcars-mtcars-filter-var_to_add > option")) + expect_existing(app_driver, "#filter_panel-active-mtcars-mtcars-filter-var_to_add > option") expect_visible("#filter_panel-active-mtcars-mtcars-filter-var_to_add > option", app_driver) }) @@ -235,11 +235,11 @@ testthat::test_that("Expanding a card shows filter choices.", { select_4_cyl <- "#filter_panel-active-mtcars-filter-4_cyl > div.filter-card-header" app_driver$click(selector = select_4_cyl) app_driver$wait_for_idle(default_idle_duration * 8) - testthat::expect_false(is_existing(app_driver, "#filter_panel-active-mtcars-filter-4_cyl-body")) + expect_not_existing(app_driver, "#filter_panel-active-mtcars-filter-4_cyl-body") select_mpg <- "#filter_panel-active-mtcars-filter-mtcars_mpg > div.filter-card-header" app_driver$click(selector = select_mpg) app_driver$wait_for_idle(default_idle_duration * 8) - testthat::expect_true(is_existing(app_driver, "#filter_panel-active-mtcars-filter-mtcars_mpg-body")) + expect_existing(app_driver, "#filter_panel-active-mtcars-filter-mtcars_mpg-body") expect_visible("#filter_panel-active-mtcars-filter-mtcars_mpg-body", app_driver) }) From 5b70d59f615f9f6d414486edd163b2aea7652249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 19 Jan 2026 15:26:19 +0000 Subject: [PATCH 6/6] chore: fix linter errors and add pipe exception --- .lintr | 3 ++- tests/testthat/helpers-shinytest2.R | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.lintr b/.lintr index 3377b6466..13fa06ef2 100644 --- a/.lintr +++ b/.lintr @@ -1,4 +1,5 @@ linters: linters_with_defaults( line_length_linter = line_length_linter(120), - object_usage_linter = NULL + object_usage_linter = NULL, + pipe_consistency_linter = NULL ) diff --git a/tests/testthat/helpers-shinytest2.R b/tests/testthat/helpers-shinytest2.R index 79f49c2d8..8ace5759a 100644 --- a/tests/testthat/helpers-shinytest2.R +++ b/tests/testthat/helpers-shinytest2.R @@ -24,7 +24,11 @@ expect_visible <- function(selector, app_driver, timeout) { { app_driver$wait_for_js( sprintf( - "Array.from(document.querySelectorAll(%s)).map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0)).some(Boolean)", + paste0( + "Array.from(document.querySelectorAll(%s))", + ".map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0))", + ".some(Boolean)" + ), selector ), timeout @@ -46,7 +50,11 @@ expect_hidden <- function(selector, app_driver, timeout) { { app_driver$wait_for_js( sprintf( - "!Array.from(document.querySelectorAll(%s)).map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0)).some(Boolean)", + paste0( + "!Array.from(document.querySelectorAll(%s))", + ".map(el => el.checkVisibility() && (el.textContent.trim().length > 0 || el.children.length > 0))", + ".some(Boolean)" + ), selector ), timeout