From be6414123eaf3717d4623b6e7a668d191e8055fb Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 14 Jul 2023 16:27:04 -0500 Subject: [PATCH 1/4] Add unit tests for bslib::tooltip() --- inst/apps/314-bslib-tooltips/app.R | 131 +++++++++++++++ inst/apps/314-bslib-tooltips/tests/testthat.R | 1 + .../tests/testthat/setup-shinytest2.R | 3 + .../tests/testthat/test-314-bslib-tooltips.R | 158 ++++++++++++++++++ 4 files changed, 293 insertions(+) create mode 100644 inst/apps/314-bslib-tooltips/app.R create mode 100644 inst/apps/314-bslib-tooltips/tests/testthat.R create mode 100644 inst/apps/314-bslib-tooltips/tests/testthat/setup-shinytest2.R create mode 100644 inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R diff --git a/inst/apps/314-bslib-tooltips/app.R b/inst/apps/314-bslib-tooltips/app.R new file mode 100644 index 0000000000..131f9d8a43 --- /dev/null +++ b/inst/apps/314-bslib-tooltips/app.R @@ -0,0 +1,131 @@ +library(shiny) +library(bslib) +library(plotly) + +ui <- page_navbar( + title = "Tooltip tests", + fillable = FALSE, + id = "navbar", + theme = bs_theme("tooltip-bg" = "#232529"), + + nav_panel( + "Tooltip cases", + inputPanel( + class = "px-3 py-5", + h3("Placement"), + tooltip( + a("auto", id = "tip-auto", href = "#"), + "Tooltip title" + ), + tooltip( + a("left", id = "tip-left", href = "#"), + "Tooltip title", + placement = "left" + ), + tooltip( + a("right", id = "tip-right", href = "#"), + "Tooltip title", + placement = "right" + ), + tooltip( + a("top", id = "tip-top", href = "#"), + "Tooltip title", + placement = "top" + ), + tooltip( + a("bottom", id = "tip-bottom", href = "#"), + "Tooltip title", + placement = "bottom" + ) + ), + + inputPanel( + class = "px-3 py-5", + h3("Triggers"), + tooltip(id = "tip-hello", + "Hello tooltip", + "Tooltip message" + ), + tooltip(id = "tip-inline", + span("Inline tooltip"), + "Tooltip message" + ), + tooltip(id = "tip-action", + actionButton("btn", "A button"), + "Tooltip 1" + ), + tooltip(id = "tip-multiple", + tagList( + actionButton("btn2", "A button"), + actionButton("btn3", "A button"), + ), + "A tooltip" + ) + ), + + inputPanel( + class = "px-3 py-5", + h3("Options"), + tooltip( + span("Offset (50,50)", id = "tip-offset"), + "This tip should appear 50px down/right", + placement = "right", + options = list(offset = c(50, 50)) + ), + tooltip( + span("Offset (50,50)", id = "tip-animation"), + "This tip shouldn't fade in/out", + placement = "right", + options = list(animation = FALSE) + ) + ), + + ), + + nav_panel( + "Tooltip updates", + layout_sidebar( + card( + card_header( + span( + "Card title with tooltip", + bsicons::bs_icon("question-circle-fill") + ) |> + tooltip( + "Tooltip message", id = "tooltip", + placement = "right" + ) + ), + plotlyOutput("bars") + ), + sidebar = list( + textInput("tooltip_msg", "Enter a tooltip message", "Tooltip message"), + actionButton("show_tooltip", "Show tooltip", class = "mb-3"), + actionButton("hide_tooltip", "Hide tooltip") + ) + ) + ), +) + +server <- function(input, output, session) { + + observeEvent(input$tooltip_msg, { + update_tooltip("tooltip", input$tooltip_msg) + }) + + observeEvent(input$show_tooltip, { + toggle_tooltip("tooltip", show = TRUE) + }) + + observeEvent(input$hide_tooltip, { + toggle_tooltip("tooltip", show = FALSE) + }) + + output$bars <- renderPlotly({ + plot_ly(diamonds, x = ~cut) + }) + +} + +shinyApp(ui, server) + diff --git a/inst/apps/314-bslib-tooltips/tests/testthat.R b/inst/apps/314-bslib-tooltips/tests/testthat.R new file mode 100644 index 0000000000..7d25b5b9e4 --- /dev/null +++ b/inst/apps/314-bslib-tooltips/tests/testthat.R @@ -0,0 +1 @@ +shinytest2::test_app() diff --git a/inst/apps/314-bslib-tooltips/tests/testthat/setup-shinytest2.R b/inst/apps/314-bslib-tooltips/tests/testthat/setup-shinytest2.R new file mode 100644 index 0000000000..e739c4dd99 --- /dev/null +++ b/inst/apps/314-bslib-tooltips/tests/testthat/setup-shinytest2.R @@ -0,0 +1,3 @@ +# Load application support files into testing environment +shinytest2::load_app_env() + diff --git a/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R b/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R new file mode 100644 index 0000000000..d3e2da536b --- /dev/null +++ b/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R @@ -0,0 +1,158 @@ +library(shinytest2) + +# Only take screenshots on mac + r-release to reduce diff noise +release <- rversions::r_release()$version +release <- paste0( + strsplit(release, ".", fixed = TRUE)[[1]][1:2], + collapse = "." +) + +is_testing_on_ci <- identical(Sys.getenv("CI"), "true") && testthat::is_testing() +is_mac_release <- identical(paste0("mac-", release), platform_variant()) + +DO_SCREENSHOT <- is_testing_on_ci && is_mac_release + + +source(system.file("helpers", "keyboard.R", package = "shinycoreci")) + +expect_focus <- function(app, selector) { + js <- sprintf( + "document.activeElement == document.querySelector('%s')", + selector + ) + expect_true(app$get_js(!!js)) + invisible(app) +} + +# Setup App ------------------------------------------------ +app <- AppDriver$new( + name = "314-bslib-tooltips", + variant = platform_variant(), + height = 800, + width = 1200, + seed = 20230714, + view = interactive(), + options = list(bslib.precompiled = FALSE), + expect_values_screenshot_args = FALSE, + screenshot_args = list(selector = "viewport", delay = 0.5) +) +withr::defer(app$stop()) + +key_press <- key_press_factory(app) + + +# Tests for the 1st tab (Tooltip cases) +test_that("Can tab focus various cases/options", { + expect_focus(app, "body") + + key_press("Tab") + expect_focus(app, ".nav-link.active") + + # Before focusing any tooltips, set up an event handler to keep track of + # the last tooltip shown + app$run_js( + '$(document).on("shown.bs.tooltip", function(e) { window.lastShown = e.target; });' + ) + + # lastShown should contain the trigger element, which we can use to find the + # actual tooltip (we just make sure it's visible). + expect_visible_tip <- function(app, selector) { + app$wait_for_js( + sprintf("window.lastShown === document.querySelector('%s')", selector) + ) + app$wait_for_js( + "var tipId = window.lastShown.getAttribute('aria-describedby'); + $(`#${tipId}:visible`).length > 0;" + ) + } + + # Placement ---------------------------------- + key_press("Tab") + expect_focus(app, "#tip-auto") + expect_visible_tip(app, "#tip-auto") + + key_press("Tab") + expect_focus(app, "#tip-left") + expect_visible_tip(app, "#tip-left") + + key_press("Tab") + expect_focus(app, "#tip-right") + expect_visible_tip(app, "#tip-right") + + key_press("Tab") + expect_focus(app, "#tip-top") + expect_visible_tip(app, "#tip-top") + + key_press("Tab") + expect_focus(app, "#tip-bottom") + expect_visible_tip(app, "#tip-bottom") + + # Triggers ---------------------------------- + key_press("Tab") + expect_focus(app, "#tip-hello span") + expect_visible_tip(app, "#tip-hello span") + + key_press("Tab") + expect_focus(app, "#tip-inline span") + expect_visible_tip(app, "#tip-inline span") + + key_press("Tab") + expect_focus(app, "#tip-action button") + expect_visible_tip(app, "#tip-action button") + + key_press("Tab") + key_press("Tab") + expect_focus(app, "#tip-multiple :last-child") + expect_visible_tip(app, "#tip-multiple :last-child") + + # Options ---------------------------------- + key_press("Tab") + expect_focus(app, "#tip-offset") + expect_visible_tip(app, "#tip-offset") + + if (DO_SCREENSHOT) app$expect_screenshot() + + key_press("Tab") + expect_focus(app, "#tip-animation") + expect_visible_tip(app, "#tip-animation") +}) + + + +# Tests for the 2nd tab (Tooltip cases) +test_that("Can programmatically update/show/hide tooltip", { + + expect_no_tip <- function(app) { + app$wait_for_js("$('.tooltip:visible').length === 0") + } + + expect_tip_message <- function(app, msg) { + app$wait_for_js( + sprintf( + "document.querySelector('.tooltip-inner').innerText === '%s'", + msg + ) + ) + } + + app$set_inputs("navbar" = "Tooltip updates") + + app$click("show_tooltip") + expect_visible_tip(app, "#tooltip span") + + app$set_inputs("tooltip_msg" = "new") + expect_tip_message(app, "new") + + app$click("hide_tooltip") + expect_no_tip(app) + + app$set_inputs("tooltip_msg" = "newer") + expect_tip_message(app, "newer") + + app$click("show_tooltip") + expect_visible_tip(app, "#tooltip span") + expect_tip_message(app, "newer") + + app$set_inputs("navbar" = "Tooltip cases") + expect_no_tip(app) +}) From 9c5006ee390688529f567f00851fe4073443bb83 Mon Sep 17 00:00:00 2001 From: Carson Date: Tue, 25 Jul 2023 18:09:49 -0500 Subject: [PATCH 2/4] Fix function scoping issue --- .../tests/testthat/test-314-bslib-tooltips.R | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R b/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R index d3e2da536b..e61e17cd27 100644 --- a/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R +++ b/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R @@ -17,7 +17,7 @@ source(system.file("helpers", "keyboard.R", package = "shinycoreci")) expect_focus <- function(app, selector) { js <- sprintf( - "document.activeElement == document.querySelector('%s')", + "document.activeElement === document.querySelector('%s')", selector ) expect_true(app$get_js(!!js)) @@ -40,6 +40,23 @@ withr::defer(app$stop()) key_press <- key_press_factory(app) +# Before focusing any tooltips, set up an event handler to keep track of +# the last tooltip shown +app$run_js( + '$(document).on("shown.bs.tooltip", function(e) { window.lastShown = e.target; });' +) + +# lastShown should contain the trigger element, which we can use to find the +# actual tooltip (we just make sure it's visible). +expect_visible_tip <- function(app, selector) { + app$wait_for_js( + sprintf("window.lastShown === document.querySelector('%s')", selector) + ) + app$wait_for_js( + "var tipId = window.lastShown.getAttribute('aria-describedby'); + $(`#${tipId}:visible`).length > 0;" + ) +} # Tests for the 1st tab (Tooltip cases) test_that("Can tab focus various cases/options", { @@ -48,24 +65,6 @@ test_that("Can tab focus various cases/options", { key_press("Tab") expect_focus(app, ".nav-link.active") - # Before focusing any tooltips, set up an event handler to keep track of - # the last tooltip shown - app$run_js( - '$(document).on("shown.bs.tooltip", function(e) { window.lastShown = e.target; });' - ) - - # lastShown should contain the trigger element, which we can use to find the - # actual tooltip (we just make sure it's visible). - expect_visible_tip <- function(app, selector) { - app$wait_for_js( - sprintf("window.lastShown === document.querySelector('%s')", selector) - ) - app$wait_for_js( - "var tipId = window.lastShown.getAttribute('aria-describedby'); - $(`#${tipId}:visible`).length > 0;" - ) - } - # Placement ---------------------------------- key_press("Tab") expect_focus(app, "#tip-auto") From 41e5b560273bf12b143353ad292127f9847b6f09 Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 26 Jul 2023 09:38:30 -0500 Subject: [PATCH 3/4] Remove unnecessary (and bad) test expectation --- .../314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R | 1 - 1 file changed, 1 deletion(-) diff --git a/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R b/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R index e61e17cd27..24256cfe16 100644 --- a/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R +++ b/inst/apps/314-bslib-tooltips/tests/testthat/test-314-bslib-tooltips.R @@ -146,7 +146,6 @@ test_that("Can programmatically update/show/hide tooltip", { expect_no_tip(app) app$set_inputs("tooltip_msg" = "newer") - expect_tip_message(app, "newer") app$click("show_tooltip") expect_visible_tip(app, "#tooltip span") From eb23b0d84ec276f9de93ccde11a7be61661b20cd Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 26 Jul 2023 10:51:22 -0500 Subject: [PATCH 4/4] Avoid the pipe operator --- inst/apps/314-bslib-tooltips/app.R | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/inst/apps/314-bslib-tooltips/app.R b/inst/apps/314-bslib-tooltips/app.R index 131f9d8a43..9e52cf3b65 100644 --- a/inst/apps/314-bslib-tooltips/app.R +++ b/inst/apps/314-bslib-tooltips/app.R @@ -87,14 +87,14 @@ ui <- page_navbar( layout_sidebar( card( card_header( - span( - "Card title with tooltip", - bsicons::bs_icon("question-circle-fill") - ) |> - tooltip( - "Tooltip message", id = "tooltip", - placement = "right" - ) + tooltip( + span( + "Card title with tooltip", + bsicons::bs_icon("question-circle-fill") + ), + "Tooltip message", id = "tooltip", + placement = "right" + ) ), plotlyOutput("bars") ),