From 26ecc25ab2dfbbbda6552e06e26f4e30c9cf32da Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Fri, 16 Oct 2026 06:41:08 +0800 Subject: [PATCH] Remove Shiny input events from their attachment container --- inst/srcjs/shinyjs-default-funcs.js | 2 + tests/testthat/apps/remove-events/app.R | 52 +++++++++++++++++++ .../remove-events/test-remove-events-shiny.R | 25 +++++++++ 3 files changed, 79 insertions(+) create mode 100644 tests/testthat/apps/remove-events/app.R create mode 100644 tests/testthat/apps/remove-events/test-remove-events-shiny.R diff --git a/inst/srcjs/shinyjs-default-funcs.js b/inst/srcjs/shinyjs-default-funcs.js index 3f09467..ee28701 100644 --- a/inst/srcjs/shinyjs-default-funcs.js +++ b/inst/srcjs/shinyjs-default-funcs.js @@ -939,6 +939,8 @@ shinyjs = function() { } } } else { + // Match the container used when attaching events to Shiny inputs. + el = $(_getContainer(el)[0]); var attrName = "data-shinyjs-" + eventType; if (el[0].hasAttribute(attrName)) { if (removeAll) { diff --git a/tests/testthat/apps/remove-events/app.R b/tests/testthat/apps/remove-events/app.R new file mode 100644 index 0000000..48438fd --- /dev/null +++ b/tests/testthat/apps/remove-events/app.R @@ -0,0 +1,52 @@ +library(shiny) +library(shinyjs) + +inputEventsUI <- function(id) { + ns <- NS(id) + tagList( + textInput(ns("field"), "Click this input"), + p(id = ns("plain"), "Click this paragraph"), + actionButton(ns("remove_one"), "Remove first input handler"), + actionButton(ns("remove_all"), "Remove remaining click handlers"), + actionButton(ns("checkpoint"), "Checkpoint"), + textOutput(ns("first")), textOutput(ns("second")), + textOutput(ns("plain_count")), textOutput(ns("status")), + textOutput(ns("checked")) + ) +} + +inputEventsServer <- function(id) { + moduleServer(id, function(input, output, session) { + first <- reactiveVal(0) + second <- reactiveVal(0) + plain_count <- reactiveVal(0) + status <- reactiveVal("ready") + checked <- reactiveVal(0) + + event_id <- onclick("field", first(first() + 1)) + onclick("field", second(second() + 1), add = TRUE) + onclick("plain", plain_count(plain_count() + 1)) + + observeEvent(input$remove_one, { + removeEvent(event_id, "field") + status("removed one") + }) + observeEvent(input$remove_all, { + removeEvent("click", "field") + removeEvent("click", "plain") + status("removed all") + }) + observeEvent(input$checkpoint, checked(checked() + 1)) + + output$first <- renderText(first()) + output$second <- renderText(second()) + output$plain_count <- renderText(plain_count()) + output$status <- renderText(status()) + output$checked <- renderText(checked()) + }) +} + +shinyApp( + fluidPage(useShinyjs(), inputEventsUI("m")), + function(input, output, session) inputEventsServer("m") +) diff --git a/tests/testthat/apps/remove-events/test-remove-events-shiny.R b/tests/testthat/apps/remove-events/test-remove-events-shiny.R new file mode 100644 index 0000000..3eb926d --- /dev/null +++ b/tests/testthat/apps/remove-events/test-remove-events-shiny.R @@ -0,0 +1,25 @@ +test_that("input events can be removed by instance and type", { + driver <- shinytest2::AppDriver$new(".", expect_values_screenshot_args = FALSE) + on.exit(driver$stop()) + driver$wait_for_js("$('#m-status').text() === 'ready'") + + driver$run_js("document.getElementById('m-field').click()") + driver$wait_for_js("$('#m-first').text() === '1' && $('#m-second').text() === '1'") + driver$run_js("document.getElementById('m-plain').click()") + driver$wait_for_js("$('#m-plain_count').text() === '1'") + + driver$click("m-remove_one") + driver$wait_for_js("$('#m-status').text() === 'removed one'") + driver$run_js("document.getElementById('m-field').click()") + driver$wait_for_js("$('#m-second').text() === '2'") + expect_equal(driver$get_value(output = "m-first"), "1") + + driver$click("m-remove_all") + driver$wait_for_js("$('#m-status').text() === 'removed all'") + driver$run_js("document.getElementById('m-field').click(); document.getElementById('m-plain').click()") + driver$click("m-checkpoint") + driver$wait_for_js("$('#m-checked').text() === '1'") + expect_equal(driver$get_value(output = "m-first"), "1") + expect_equal(driver$get_value(output = "m-second"), "2") + expect_equal(driver$get_value(output = "m-plain_count"), "1") +})