Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@

- Improve common chunk detection, output `na_group` and `row_in_group` when there are missing values.

# Changes in version 2025.10.6 (PR#246)

- Added validation for selector names to prevent browser rendering failures. Selector names (from data values used in `clickSelects` and `showSelected`) cannot contain CSS special characters like `#`, `@`, `!`, `$`, etc., as these interfere with JavaScript DOM selectors and cause blank visualizations in the browser. The compiler now stops with a clear error message identifying problematic selector names, helping users fix data issues before attempting to render.

# Changes in version 2025.10.3 (PR#240)

- `guide_legend(override.aes)` works in a plot with both color and fill legends.
Expand Down
3 changes: 3 additions & 0 deletions R/z_animint.R
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,9 @@ animint2dir <- function
## For a static data viz with no interactive aes, no need to check
## for trivial showSelected variables with only 1 level.
checkSingleShowSelectedValue(meta$selectors)

## Check selector names for CSS compatibility (no special characters like #)
checkSelectorNames(meta$selectors)

## Go through options and add to the list.
for(v.name in names(meta$duration)){
Expand Down
21 changes: 21 additions & 0 deletions R/z_animintHelpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,27 @@ checkSingleShowSelectedValue <- function(selectors){
}


#' Validate selector names for CSS compatibility
#' @param selectors selectors to validate
#' @return \code{NULL}. Throws error if invalid characters found.
checkSelectorNames <- function(selectors){
selector.names <- names(selectors)
## Characters that are invalid in CSS selectors and cause issues in browser
## ] must be first in character class, [ can be anywhere after
invalid.pattern <- "[][#!@$%^&*=|/'\"`?<>()\\\\]"
has.invalid <- grepl(invalid.pattern, selector.names)
if(any(has.invalid)){
invalid.names <- selector.names[has.invalid]
stop(
"Invalid character(s) in selector name(s).\n",
"Selector names cannot contain special characters that interfere with CSS selectors.\n",
"The following selector(s) contain invalid characters:\n",
paste("-", invalid.names, collapse="\n"),
"\n\nPlease remove or replace these characters in your variable names.")
}
}


#' Set plot width and height for all plots
#' @param meta meta object with all information
#' @param AllPlotsInfo plot info list
Expand Down
126 changes: 126 additions & 0 deletions tests/testthat/test-compiler-invalid-selector-characters.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
context("Invalid selector character validation")

# Test that selector names with special characters cause errors
# Note: Selector names come from the VALUES in the data when using .variable/.value pattern
test_that("selector name with # causes error in clickSelects", {
viz <- list(
plot1 = ggplot() +
geom_point(
aes(Sepal.Length, Petal.Length),
data = data.frame(
Sepal.Length = 1:10,
Petal.Length = rnorm(10),
regularization = "# nearest neighbors", # This VALUE becomes the selector name
parameter = 1:10
),
clickSelects = c(regularization = "parameter")
)
)
expect_error(
animint2dir(viz, open.browser = FALSE),
"Invalid character\\(s\\) in selector name\\(s\\)",
info = "Selector names with '#' should cause an error"
)
expect_error(
animint2dir(viz, open.browser = FALSE),
"# nearest neighbors",
info = "Error message should mention the problematic selector name"
)
})

test_that("selector name with @ causes error in clickSelects", {
viz <- list(
plot1 = ggplot() +
geom_point(
aes(Sepal.Length, Petal.Length),
data = data.frame(
Sepal.Length = 1:10,
Petal.Length = rnorm(10),
regularization = "model@version1", # This VALUE becomes selector name
parameter = 1:10
),
clickSelects = c(regularization = "parameter")
)
)
expect_error(
animint2dir(viz, open.browser = FALSE),
"Invalid character\\(s\\) in selector name\\(s\\)",
info = "Selector names with '@' should cause an error"
)
expect_error(
animint2dir(viz, open.browser = FALSE),
"model@version",
info = "Error message should mention the problematic selector"
)
})

test_that("selector name with ! causes error", {
viz <- list(
plot1 = ggplot() +
geom_point(
aes(x=1:10, y=rnorm(10)),
data = data.frame(
model = "model!important",
parameter = 1:10
),
clickSelects = c(model = "parameter")
)
)
expect_error(
animint2dir(viz, open.browser = FALSE),
"Invalid character\\(s\\) in selector name\\(s\\)",
info = "Selector names with '!' should cause an error"
)
})

test_that("valid selector names work with clickSelects", {
viz <- list(
plot1 = ggplot() +
geom_point(
aes(x=1:10, y=rnorm(10)),
data = data.frame(
regularization = "polynomial_degree", # Valid name
parameter = 0:9
),
clickSelects = c(regularization = "parameter")
)
)
info <- animint2dir(viz, open.browser = FALSE)
expect_true(TRUE, info = "Valid selector names should not cause errors")
})

test_that("selector names with spaces work", {
viz <- list(
plot1 = ggplot() +
geom_point(
aes(x=1:10, y=rnorm(10)),
data = data.frame(
regularization = "nearest neighbors", # spaces are OK
parameter = 1:10
),
clickSelects = c(regularization = "parameter")
)
)
info <- animint2dir(viz, open.browser = FALSE)
expect_true(TRUE, info = "Selector names with spaces should work")
})

test_that("multiple values with invalid characters all reported", {
viz <- list(
plot1 = ggplot() +
geom_point(
aes(x=1:10, y=rnorm(10)),
data = data.frame(
regularization = rep(c("#bad", "!worse"), 5), # Both have invalid chars
parameter = 1:10
),
clickSelects = c(regularization = "parameter")
)
)
error_msg <- tryCatch(
animint2dir(viz, open.browser = FALSE),
error = function(e) as.character(e$message)
)
expect_match(error_msg, "Invalid character", info = "Should report invalid characters")
expect_match(error_msg, "#bad|!worse", info = "Should mention at least one problematic selector")
})
Loading