From 701372dcdb75ab08625a1da4848b8fa994a4ec13 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, 27 Feb 2026 01:21:50 +0000 Subject: [PATCH 1/4] feat: interaction variable --- R/0-call_utils.R | 28 ++++++++++++- R/0-interaction.R | 63 ++++++++++++++++++++++++++++ R/0-module_merge.R | 10 +++-- R/0-resolver.R | 20 +++++++++ tests/testthat/test-0-interaction.R | 26 ++++++++++++ tests/testthat/test-0-module_picks.R | 26 ++++++++++++ 6 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 R/0-interaction.R create mode 100644 tests/testthat/test-0-interaction.R diff --git a/R/0-call_utils.R b/R/0-call_utils.R index 65a2347a..346f8b6e 100644 --- a/R/0-call_utils.R +++ b/R/0-call_utils.R @@ -18,7 +18,7 @@ call_check_parse_varname <- function(varname) { if (is.character(varname)) { parsed <- parse(text = varname, keep.source = FALSE) if (length(parsed) == 1) { - varname <- parsed[[1]] + varname <- as.name(varname) } else { stop( sprintf( @@ -80,7 +80,7 @@ call_condition_choice <- function(varname, choices) { # c_call needed because it needs to be vector call # instead of vector. SummarizedExperiment.subset # handles only vector calls - call("%in%", varname, c_call) + call("%in%", as.name(varname), c_call) } } @@ -275,3 +275,27 @@ calls_combine_by <- function(operator, calls) { ) ) } + +.call_interaction_var <- function(variables, interactive_ix, dataname, sep = ":") { + select_new <- variables[interactive_ix] + select_tmp <- unique(unlist(strsplit(select_new, sep))) + select_call <- .call_dplyr_select(dataname = dataname, variables = c(variables[!interactive_ix], select_tmp)) + + mutate_args <- lapply(rlang::set_names(select_new), function(new_var) { + vars_to_interact <- strsplit(new_var, sep)[[1]] + as.call( + c(list(quote(paste)), lapply(vars_to_interact, as.name), list(sep = ":")) + ) + }) + calls_combine_by( + "%>%", + c( + select_call, + as.call(rlang::list2(str2lang("dplyr::mutate"), !!!mutate_args)), + as.call(rlang::list2( + str2lang("dplyr::select"), + substitute(!all_of(vars), env = list(vars = select_tmp)) + )) + ) + ) +} \ No newline at end of file diff --git a/R/0-interaction.R b/R/0-interaction.R new file mode 100644 index 00000000..6b19eb11 --- /dev/null +++ b/R/0-interaction.R @@ -0,0 +1,63 @@ +#' Declare interaction variable pairs for tidyselect +#' +#' Used inside tidyselect expressions to declare a pair of variables that +#' interact with each other. The pair is recorded in the selection environment +#' and the positions of both variables within the available variables are +#' returned. +#' +#' @param var1 An unquoted variable name. +#' @param var2 An unquoted variable name that interacts with `var1`. +#' @param vars Character vector of available variable names, retrieved +#' automatically via [tidyselect::peek_vars()]. +#' +#' @return An integer vector of length 2 giving the positions of `var1` and +#' `var2` in `vars`, or `NA` where a variable is not found. +#' +#' @export +interaction_vars <- function(var1, var2, vars = tidyselect::peek_vars(fn = "my_helper")) { + interaction_vars <- c(as.character(substitute(var1)), as.character(substitute(var2))) + result <- vctrs::vec_match(interaction_vars, vars) + select_env$interaction_vars <- c(select_env$interaction_vars %||% list(), list(interaction_vars)) # Option 2 + result +} + +# Environment to store interaction variable pairs during tidyselect evaluation +# This is used to communicate between the `interaction_vars()` function and the resolver that +# processes the picks with variables that interact. +# The resolver will look for this information in the environment to know which variables are +# meant to interact and need to be combined in the data. +select_env <- new.env(parent = emptyenv()) + +#' Find all `interactive_vars` calls in an expression +#' +#' Traverses an expression tree using a breadth-first search and collects the +#' arguments of every `interactive_vars()` call found. +#' +#' @param expr An R expression or quosure to search. +#' +#' @return A list of argument lists, one element per `interactive_vars()` call +#' found. Each element is a list of the unevaluated arguments passed to +#' `interactive_vars()`. +#' +#' @noRd +.find_interactive_vars <- function(expr) { + expr <- if (rlang::is_quosure(expr)) rlang::quo_get_expr(expr) else expr + + queue <- list(expr) + results <- list() + + while (length(queue) > 0) { + node <- queue[[1]] + queue <- queue[-1] + + if (rlang::is_call(node, "interaction_vars")) { + results <- c(results, list(as.list(node)[-1])) + } else if (is.call(node)) { + # Add all child nodes to the queue + queue <- c(queue, as.list(node)[-1]) + } + } + + results + +} diff --git a/R/0-module_merge.R b/R/0-module_merge.R index 664edca8..5752bcde 100644 --- a/R/0-module_merge.R +++ b/R/0-module_merge.R @@ -291,7 +291,13 @@ merge_srv <- function(id, ) this_variables <- this_variables[!duplicated(unname(this_variables))] # because unique drops names - this_call <- .call_dplyr_select(dataname = dataname, variables = this_variables) + interaction_ix <- grepl(":", this_variables) + this_call <- if (any(interaction_ix)) { + .call_interaction_var(this_variables, interaction_ix, dataname) + } else { + .call_dplyr_select(dataname = dataname, variables = this_variables) + } + this_call <- calls_combine_by("%>%", c(this_call, .call_dplyr_filter(this_filter_mapping))) if (i > 1) { @@ -401,7 +407,6 @@ merge_srv <- function(id, ) join_keys <- c(this_join_keys, join_keys) - mapping_ds <- mapping_by_dataset[[dataname]] mapping_ds <- lapply(mapping_ds, function(x) { new_vars <- .suffix_duplicated_vars( @@ -429,7 +434,6 @@ merge_srv <- function(id, anl_colnames <- union(anl_colnames, .fk(join_keys, "anl")) } - list(mapping = mapping, join_keys = join_keys) } diff --git a/R/0-resolver.R b/R/0-resolver.R index 034d6521..02a51b4d 100644 --- a/R/0-resolver.R +++ b/R/0-resolver.R @@ -67,7 +67,27 @@ determine.variables <- function(x, data) { return(list(x = .nullify_pick(x))) } + # Option 1 + # interaction_vars <- .find_interaction_vars(x$choices) + # x$choices <- .determine_choices(x$choices, data = data) + + # Option 2 + old <- select_env$interaction_vars + # withr::defer(select_env$interaction_vars <- old) + on.exit(select_env$interaction_vars <- old) + x$choices <- .determine_choices(x$choices, data = data) + # change data to add columns that combine interaction vars + interaction_vars <- select_env$interaction_vars + # End of options + + for (ix in seq_along(interaction_vars)) { + new_choice <- rlang::set_names(paste(interaction_vars[[ix]], collapse = ":")) + data <- data |> + dplyr::mutate(!!new_choice := paste(.data[[interaction_vars[[ix]][[1]]]], .data[[interaction_vars[[ix]][[2]]]], sep = ":")) + x$choices <- c(x$choices, new_choice) + } + x$selected <- .determine_selected( x$selected, data = data[intersect(x$choices, colnames(data))], diff --git a/tests/testthat/test-0-interaction.R b/tests/testthat/test-0-interaction.R new file mode 100644 index 00000000..1cd788f6 --- /dev/null +++ b/tests/testthat/test-0-interaction.R @@ -0,0 +1,26 @@ +testthat::test_that("interaction_vars is compatible with eval_select", { + expect_equal( + unname( + tidyselect::eval_select( + interaction_vars("AGE", "RACE"), + data = teal.data::rADSL + ) + ), + which(colnames(teal.data::rADSL) %in% c("AGE", "RACE")) + ) +}) + +testthat::test_that("interaction_vars stores interactions in environment", { + old <- select_env$interaction_vars + withr::defer(select_env$interaction_vars <- old) + select_env$interaction_vars <- NULL + + tidyselect::eval_select( + c(interaction_vars(AGE, RACE), interaction_vars(AGE, COUNTRY)), + data = teal.data::rADSL + ) + expect_equal( + select_env$interaction_vars, + list(c("AGE", "RACE"), c("AGE", "COUNTRY")) + ) +}) diff --git a/tests/testthat/test-0-module_picks.R b/tests/testthat/test-0-module_picks.R index 3276971d..e621f568 100644 --- a/tests/testthat/test-0-module_picks.R +++ b/tests/testthat/test-0-module_picks.R @@ -567,6 +567,32 @@ testthat::describe("picks_srv resolves picks", { ) }) + it("picks with delayed interaction_vars are resolved", { + test_picks <- suppressWarnings( + picks( + datasets(choices = "iris", selected = "iris"), + variables(choices = interaction_vars("Species", "Petal.Length"), selected = "Species:Petal.Length") + ) + ) + + shiny::testServer( + picks_srv, + args = list(id = "id", picks = test_picks, data = shiny::reactive(list(iris = iris))), + expr = { + suppressWarnings( + picks_expected <- picks( + datasets(choices = c(iris = "iris"), selected = "iris"), + variables( + choices = rlang::set_names(c("Species", "Petal.Length", "Species:Petal.Length")), + selected = "Species:Petal.Length" + ) + ) + ) + testthat::expect_identical(picks_resolved(), picks_expected) + } + ) + }) + it("pick elements are resolved sequentially", { test_picks <- picks( datasets(choices = tidyselect::where(is.data.frame), selected = 1L), From f63d03401628b6392572aa99a60dd10799bd39fc 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, 27 Feb 2026 18:38:40 +0000 Subject: [PATCH 2/4] feat: dynamic operators without any specific hardcoded logic --- NAMESPACE | 1 + R/0-call_utils.R | 22 ++++++++------- R/0-interaction.R | 42 ++++++++++++++++++++++++++--- R/0-module_merge.R | 12 ++++++--- R/0-resolver.R | 17 ++++++------ tests/testthat/test-0-interaction.R | 8 +++--- 6 files changed, 71 insertions(+), 31 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index dd6ca8d6..bb2785d0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -81,6 +81,7 @@ export(get_dataset_prefixed_col_names) export(get_extract_datanames) export(get_merge_call) export(get_relabel_call) +export(interaction_vars) export(is.choices_selected) export(is_categorical) export(is_single_dataset) diff --git a/R/0-call_utils.R b/R/0-call_utils.R index 346f8b6e..3ebe0bad 100644 --- a/R/0-call_utils.R +++ b/R/0-call_utils.R @@ -276,17 +276,19 @@ calls_combine_by <- function(operator, calls) { ) } -.call_interaction_var <- function(variables, interactive_ix, dataname, sep = ":") { - select_new <- variables[interactive_ix] - select_tmp <- unique(unlist(strsplit(select_new, sep))) - select_call <- .call_dplyr_select(dataname = dataname, variables = c(variables[!interactive_ix], select_tmp)) +.call_mutate_operators <- function(variables, operators_ix, dataname, operators) { + operators <- rlang::set_names(operators, vapply(operators, attr, which = "var_name", FUN.VALUE = character(1))) + select_new <- variables[operators_ix] + select_tmp <- unname(unlist(operators[select_new])) + select_call <- .call_dplyr_select( + dataname = dataname, + variables = c(variables[!operators_ix], select_tmp) + ) - mutate_args <- lapply(rlang::set_names(select_new), function(new_var) { - vars_to_interact <- strsplit(new_var, sep)[[1]] - as.call( - c(list(quote(paste)), lapply(vars_to_interact, as.name), list(sep = ":")) - ) + mutate_args <- lapply(select_new, function(new_var) { + .operator_mutate_args(operators[[new_var]]) }) + calls_combine_by( "%>%", c( @@ -294,7 +296,7 @@ calls_combine_by <- function(operator, calls) { as.call(rlang::list2(str2lang("dplyr::mutate"), !!!mutate_args)), as.call(rlang::list2( str2lang("dplyr::select"), - substitute(!all_of(vars), env = list(vars = select_tmp)) + substitute(!all_of(vars), env = list(vars = select_tmp[!select_tmp %in% variables])) )) ) ) diff --git a/R/0-interaction.R b/R/0-interaction.R index 6b19eb11..e198150a 100644 --- a/R/0-interaction.R +++ b/R/0-interaction.R @@ -14,13 +14,47 @@ #' `var2` in `vars`, or `NA` where a variable is not found. #' #' @export -interaction_vars <- function(var1, var2, vars = tidyselect::peek_vars(fn = "my_helper")) { - interaction_vars <- c(as.character(substitute(var1)), as.character(substitute(var2))) - result <- vctrs::vec_match(interaction_vars, vars) - select_env$interaction_vars <- c(select_env$interaction_vars %||% list(), list(interaction_vars)) # Option 2 +interaction_vars <- function(var1, var2, vars = tidyselect::peek_vars(fn = "interaction_vars")) { + new_var <- c(as.character(substitute(var1)), as.character(substitute(var2))) + result <- vctrs::vec_match(new_var, vars) + new_operator <- structure( + new_var, + class = "interaction", + var_name = sprintf("%s:%s", new_var[[1]], new_var[[2]]) + ) # Option 2 + select_env$operators <- select_env$operators %||% list() + select_env$operators[[length(select_env$operators) + 1]] <- new_operator result } +.operator_mutate <- function(x, new_choice, data) { + UseMethod(".operator_mutate") +} + +#' @method .operator_mutate interaction +#' @keywords internal +.operator_mutate.interaction <- function(x, new_choice, data) { + checkmate::assert_character(x, len = 2) + checkmate::assert_string(new_choice) + checkmate::assert_data_frame(data) + dplyr::mutate( + data, + !!new_choice := paste(.data[[x[[1]]]], .data[[x[[2]]]], sep = ":") + ) +} + +.operator_mutate_args <- function(x) { + UseMethod(".operator_mutate_args") +} + +#' @method .operator_mutate interaction +#' @keywords internal +.operator_mutate_args.interaction <- function(x) { + checkmate::assert_character(x, len = 2) + as.call(c(list(quote(paste)), lapply(x, as.name), list(sep = ":"))) +} + + # Environment to store interaction variable pairs during tidyselect evaluation # This is used to communicate between the `interaction_vars()` function and the resolver that # processes the picks with variables that interact. diff --git a/R/0-module_merge.R b/R/0-module_merge.R index 5752bcde..47de38eb 100644 --- a/R/0-module_merge.R +++ b/R/0-module_merge.R @@ -290,10 +290,12 @@ merge_srv <- function(id, unlist(lapply(unname(this_mapping), `[[`, "variables")) ) this_variables <- this_variables[!duplicated(unname(this_variables))] # because unique drops names + operators <- unlist(lapply(unname(this_mapping), "[[", i = "operators"), recursive = FALSE) + operators_ix <- this_variables %in% + vapply(operators, attr, which = "var_name", FUN.VALUE = character(1)) - interaction_ix <- grepl(":", this_variables) - this_call <- if (any(interaction_ix)) { - .call_interaction_var(this_variables, interaction_ix, dataname) + this_call <- if (any(operators_ix)) { + .call_mutate_operators(this_variables, operators_ix, dataname, operators) } else { .call_dplyr_select(dataname = dataname, variables = this_variables) } @@ -344,9 +346,11 @@ merge_srv <- function(id, mapping <- lapply( # what has been selected in each selector selectors, function(selector) { - lapply(selector, function(x) { + result <- lapply(selector, function(x) { stats::setNames(x$selected, x$selected) }) + result$operators <- selector$variables$operators + result } ) diff --git a/R/0-resolver.R b/R/0-resolver.R index 02a51b4d..1c4f7b1d 100644 --- a/R/0-resolver.R +++ b/R/0-resolver.R @@ -68,24 +68,23 @@ determine.variables <- function(x, data) { } # Option 1 - # interaction_vars <- .find_interaction_vars(x$choices) + # custom_operators <- .find_interaction_vars(x$choices) # x$choices <- .determine_choices(x$choices, data = data) # Option 2 - old <- select_env$interaction_vars - # withr::defer(select_env$interaction_vars <- old) - on.exit(select_env$interaction_vars <- old) + old <- select_env$operators + on.exit(select_env$operators <- old) x$choices <- .determine_choices(x$choices, data = data) # change data to add columns that combine interaction vars - interaction_vars <- select_env$interaction_vars + custom_operators <- select_env$operators # End of options - for (ix in seq_along(interaction_vars)) { - new_choice <- rlang::set_names(paste(interaction_vars[[ix]], collapse = ":")) - data <- data |> - dplyr::mutate(!!new_choice := paste(.data[[interaction_vars[[ix]][[1]]]], .data[[interaction_vars[[ix]][[2]]]], sep = ":")) + for (ix in seq_along(custom_operators)) { + new_choice <- rlang::set_names(attr(custom_operators[[ix]], "var_name", TRUE)) + data <- .operator_mutate(custom_operators[[ix]], new_choice, data) x$choices <- c(x$choices, new_choice) + x$operators <- custom_operators } x$selected <- .determine_selected( diff --git a/tests/testthat/test-0-interaction.R b/tests/testthat/test-0-interaction.R index 1cd788f6..8c3be00e 100644 --- a/tests/testthat/test-0-interaction.R +++ b/tests/testthat/test-0-interaction.R @@ -11,16 +11,16 @@ testthat::test_that("interaction_vars is compatible with eval_select", { }) testthat::test_that("interaction_vars stores interactions in environment", { - old <- select_env$interaction_vars - withr::defer(select_env$interaction_vars <- old) - select_env$interaction_vars <- NULL + old <- select_env$custom_operators + withr::defer(select_env$custom_operators <- old) + select_env$custom_operators <- NULL tidyselect::eval_select( c(interaction_vars(AGE, RACE), interaction_vars(AGE, COUNTRY)), data = teal.data::rADSL ) expect_equal( - select_env$interaction_vars, + select_env$custom_operators, list(c("AGE", "RACE"), c("AGE", "COUNTRY")) ) }) From 1fb5264ae60d0a7f465229db9365d4a0b03963b4 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, 27 Feb 2026 18:47:28 +0000 Subject: [PATCH 3/4] cleanup: improvement and removal of redundat mutate operation definition --- R/0-call_utils.R | 2 +- R/0-interaction.R | 38 ++------------------------------------ R/0-resolver.R | 6 ------ 3 files changed, 3 insertions(+), 43 deletions(-) diff --git a/R/0-call_utils.R b/R/0-call_utils.R index 3ebe0bad..e9c0af50 100644 --- a/R/0-call_utils.R +++ b/R/0-call_utils.R @@ -282,7 +282,7 @@ calls_combine_by <- function(operator, calls) { select_tmp <- unname(unlist(operators[select_new])) select_call <- .call_dplyr_select( dataname = dataname, - variables = c(variables[!operators_ix], select_tmp) + variables = unique(c(variables[!operators_ix], select_tmp)) ) mutate_args <- lapply(select_new, function(new_var) { diff --git a/R/0-interaction.R b/R/0-interaction.R index e198150a..4b11b5b3 100644 --- a/R/0-interaction.R +++ b/R/0-interaction.R @@ -21,7 +21,7 @@ interaction_vars <- function(var1, var2, vars = tidyselect::peek_vars(fn = "inte new_var, class = "interaction", var_name = sprintf("%s:%s", new_var[[1]], new_var[[2]]) - ) # Option 2 + ) select_env$operators <- select_env$operators %||% list() select_env$operators[[length(select_env$operators) + 1]] <- new_operator result @@ -39,7 +39,7 @@ interaction_vars <- function(var1, var2, vars = tidyselect::peek_vars(fn = "inte checkmate::assert_data_frame(data) dplyr::mutate( data, - !!new_choice := paste(.data[[x[[1]]]], .data[[x[[2]]]], sep = ":") + !!new_choice := rlang::eval_bare(.operator_mutate_args(x)) ) } @@ -61,37 +61,3 @@ interaction_vars <- function(var1, var2, vars = tidyselect::peek_vars(fn = "inte # The resolver will look for this information in the environment to know which variables are # meant to interact and need to be combined in the data. select_env <- new.env(parent = emptyenv()) - -#' Find all `interactive_vars` calls in an expression -#' -#' Traverses an expression tree using a breadth-first search and collects the -#' arguments of every `interactive_vars()` call found. -#' -#' @param expr An R expression or quosure to search. -#' -#' @return A list of argument lists, one element per `interactive_vars()` call -#' found. Each element is a list of the unevaluated arguments passed to -#' `interactive_vars()`. -#' -#' @noRd -.find_interactive_vars <- function(expr) { - expr <- if (rlang::is_quosure(expr)) rlang::quo_get_expr(expr) else expr - - queue <- list(expr) - results <- list() - - while (length(queue) > 0) { - node <- queue[[1]] - queue <- queue[-1] - - if (rlang::is_call(node, "interaction_vars")) { - results <- c(results, list(as.list(node)[-1])) - } else if (is.call(node)) { - # Add all child nodes to the queue - queue <- c(queue, as.list(node)[-1]) - } - } - - results - -} diff --git a/R/0-resolver.R b/R/0-resolver.R index 1c4f7b1d..4d7e071d 100644 --- a/R/0-resolver.R +++ b/R/0-resolver.R @@ -67,18 +67,12 @@ determine.variables <- function(x, data) { return(list(x = .nullify_pick(x))) } - # Option 1 - # custom_operators <- .find_interaction_vars(x$choices) - # x$choices <- .determine_choices(x$choices, data = data) - - # Option 2 old <- select_env$operators on.exit(select_env$operators <- old) x$choices <- .determine_choices(x$choices, data = data) # change data to add columns that combine interaction vars custom_operators <- select_env$operators - # End of options for (ix in seq_along(custom_operators)) { new_choice <- rlang::set_names(attr(custom_operators[[ix]], "var_name", TRUE)) From 830857cb25fbb45118fd3353c34aabccdc59f82a 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, 27 Feb 2026 19:02:34 +0000 Subject: [PATCH 4/4] fix: tests --- man/interaction_vars.Rd | 30 ++++++++++++++++++++++++++++ tests/testthat/test-0-interaction.R | 13 +++++++----- tests/testthat/test-0-module_picks.R | 5 +++++ 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 man/interaction_vars.Rd diff --git a/man/interaction_vars.Rd b/man/interaction_vars.Rd new file mode 100644 index 00000000..980b3d53 --- /dev/null +++ b/man/interaction_vars.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/0-interaction.R +\name{interaction_vars} +\alias{interaction_vars} +\title{Declare interaction variable pairs for tidyselect} +\usage{ +interaction_vars( + var1, + var2, + vars = tidyselect::peek_vars(fn = "interaction_vars") +) +} +\arguments{ +\item{var1}{An unquoted variable name.} + +\item{var2}{An unquoted variable name that interacts with \code{var1}.} + +\item{vars}{Character vector of available variable names, retrieved +automatically via \code{\link[tidyselect:peek_vars]{tidyselect::peek_vars()}}.} +} +\value{ +An integer vector of length 2 giving the positions of \code{var1} and +\code{var2} in \code{vars}, or \code{NA} where a variable is not found. +} +\description{ +Used inside tidyselect expressions to declare a pair of variables that +interact with each other. The pair is recorded in the selection environment +and the positions of both variables within the available variables are +returned. +} diff --git a/tests/testthat/test-0-interaction.R b/tests/testthat/test-0-interaction.R index 8c3be00e..e55c2385 100644 --- a/tests/testthat/test-0-interaction.R +++ b/tests/testthat/test-0-interaction.R @@ -11,16 +11,19 @@ testthat::test_that("interaction_vars is compatible with eval_select", { }) testthat::test_that("interaction_vars stores interactions in environment", { - old <- select_env$custom_operators - withr::defer(select_env$custom_operators <- old) - select_env$custom_operators <- NULL + old <- select_env$operators + withr::defer(select_env$operators <- old) + select_env$operators <- NULL tidyselect::eval_select( c(interaction_vars(AGE, RACE), interaction_vars(AGE, COUNTRY)), data = teal.data::rADSL ) expect_equal( - select_env$custom_operators, - list(c("AGE", "RACE"), c("AGE", "COUNTRY")) + select_env$operators, + list( + structure(c("AGE", "RACE"), class = "interaction", var_name = "AGE:RACE"), + structure(c("AGE", "COUNTRY"), class = "interaction", var_name = "AGE:COUNTRY") + ) ) }) diff --git a/tests/testthat/test-0-module_picks.R b/tests/testthat/test-0-module_picks.R index e621f568..7deb32e4 100644 --- a/tests/testthat/test-0-module_picks.R +++ b/tests/testthat/test-0-module_picks.R @@ -588,6 +588,11 @@ testthat::describe("picks_srv resolves picks", { ) ) ) + picks_expected$variables$operators <- list( + structure( + c("Species", "Petal.Length"), class = "interaction", var_name = "Species:Petal.Length" + ) + ) testthat::expect_identical(picks_resolved(), picks_expected) } )