tests/testthat/test-eval-walk.R

test_that("leaves of data expression tree are evaluated in the context", {
  wrapper <- function(x, var) select_loc(x, {{ var }}:length(x))
  expect_identical(wrapper(letters2, x), select_loc(letters2, x:26))

  wrapper <- function(x, var) select_loc(x, -({{ var }}:length(x)))
  expect_identical(wrapper(letters2, x), select_loc(letters2, -(x:26)))

  wrapper <- function(x, var1, var2) select_loc(x, c(-{{ var1 }}, -({{ var2 }}:length(x))))
  expect_identical(wrapper(letters2, a, c), select_loc(letters2, c(-a, -(c:26))))
})

test_that("dots passed to `c()` are evaluated in their context", {
  wrapper <- function(x, ...) {
    select_loc(x, c(x, length(x), ...))
  }
  f <- function(x, ...) {
    a <- 13
    g(x, ..., identity(a))
  }
  g <- function(x, ...) {
    a <- 15
    wrapper(x, ..., identity(a))
  }
  expect_identical(f(letters2, e, 10), select_loc(letters2, c(x, 26, e, 10, 13, 15)))
})

test_that("quosures can be used in data expressions", {
  expect_identical(select_loc(letters2, !!quo(a)), select_loc(letters2, a))
  expect_identical(select_loc(letters2, !!quo(a:!!quo(c))), select_loc(letters2, a:c))
  expect_identical(select_loc(letters2, !!quo(c(!!quo(a)))), select_loc(letters2, a))
})

test_that("quosures update the current context", {
  quo <- local({
    `_foo` <- 24
    quo(a:identity(`_foo`))
  })
  expect_identical(select_loc(letters2, !!quo(c(-(!!quo)))), select_loc(letters2, -(a:24)))
})

test_that("data expressions support character vectors (#78)", {
  expect_identical(select_loc(letters2, -identity(letters2[2:5])), select_loc(letters2, -(2:5)))
  expect_identical(select_loc(letters2, identity("a"):identity("c")), select_loc(letters2, a:c))
  expect_identical(select_loc(letters2, (identity(letters2[[1]]))), select_loc(letters2, a))
  expect_identical(select_loc(letters2, c(identity(letters2[[1]]))), select_loc(letters2, a))
})

test_that("boolean operators are overloaded", {
  expect_identical(
    select_loc(letters2, starts_with("a") & ends_with("a")),
    select_loc(letters2, intersect(starts_with("a"), ends_with("a"))),
  )

  expect_identical(
    select_loc(letters2, starts_with("a") | ends_with("c")),
    select_loc(letters2, c(starts_with("a"), ends_with("c")))
  )

  expect_identical(
    select_loc(letters2, starts_with("a") | ends_with("c") | contains("z")),
    select_loc(letters2, c(starts_with("a"), ends_with("c"), contains("z")))
  )

  expect_identical(
    select_loc(letters2, (starts_with("a") | ends_with("c")) & contains("a")),
    select_loc(letters2, intersect(c(starts_with("a"), ends_with("c")), contains("a")))
  )

  expect_identical(
    select_loc(letters2, !(starts_with("a") | ends_with("c"))),
    select_loc(letters2, -(starts_with("a") | ends_with("c"))),
  )

  # This pattern is not possible with `intersect()` because its
  # arguments are evaluated in non-data context
  expect_error(
    select_loc(letters2, intersect(c(starts_with("a"), ends_with("c")), b:d)),
    "not found"
  )
  expect_identical(
    select_loc(letters2, (starts_with("a") | ends_with("c")) & b:d),
    select_loc(letters2, c)
  )

  expect_identical(
    select_loc(letters2, (starts_with("a") | ends_with("c")) | i:k),
    select_loc(letters2, c(c(starts_with("a"), ends_with("c")), i:k)),
  )
})

test_that("scalar boolean operators fail informatively", {
  expect_snapshot(error = TRUE, {
    select_loc(letters2, starts_with("a") || ends_with("b"))
    select_loc(letters2, starts_with("a") && ends_with("b"))
  })
})

test_that("can use `+` in env context", {
  foo <- 1
  expect_identical(select_loc(letters2, foo + 2), c(c = 3L))
  expect_error(select_loc(letters2, a + 2), "not found")
})

test_that("can use `-` in env context", {
  expect_identical(
    select_loc(iris, 1:(ncol(iris) - 2)),
    select_loc(iris, 1:3)
  )
})

test_that("can't use `*` and `^` in data context", {
  expect_error(select_loc(letters2, a * 2), "arithmetic")
  expect_error(select_loc(letters2, a^2), "arithmetic")

  expect_snapshot(error = TRUE, {
    select_loc(letters2, a * 2)
    select_loc(letters2, a^2)
  })
})

test_that("can use arithmetic operators in non-data context", {
  expect_identical(select_loc(letters2, identity(2 * 2 + 2^2 / 2)), c(f = 6L))
})

test_that("symbol lookup outside data informs caller about better practice", {
  expect_snapshot({
    vars <- c("a", "b")
    select_loc(letters2, vars)
  })
})

test_that("selection helpers are in the context mask", {
  out <- local(envir = env(base_env()), {
    letters2 <- rlang::set_names(letters)
    tidyselect::eval_select(quote(all_of("a")), letters2)
  })
  expect_identical(out, c(a = 1L))
})

test_that("non-strict evaluation allows unknown variables", {
  expect_identical(
    select_loc(letters2, identity("foo"), strict = FALSE),
    select_loc(letters2, int())
  )
  expect_identical(
    select_loc(letters2, identity(100), strict = FALSE),
    select_loc(letters2, int())
  )
  expect_identical(
    select_loc(letters2, -identity("foo"), strict = FALSE),
    select_loc(letters2, -int())
  )
  expect_identical(
    select_loc(letters2, -identity(100), strict = FALSE),
    select_loc(letters2, -int())
  )
})

test_that("can use predicates in selections", {
  expect_identical(select_loc(iris, where(is.factor)), c(Species = 5L))
  expect_identical(select_loc(iris, where(is.numeric)), set_names(1:4, names(iris)[1:4]))
  expect_identical(select_loc(iris, where(is.numeric) & where(is.factor)), set_names(int(), chr()))
  expect_identical(select_loc(iris, where(is.numeric) | where(is.factor)), set_names(1:5, names(iris)))
})

test_that("can forbid use of predicates", {
  expect_snapshot(
    select_loc(iris, where(is.factor), allow_predicates = FALSE),
    error = TRUE
  )
})

test_that("inline functions are allowed", {
  expect_identical(
    select_loc(iris, !!is.numeric),
    select_loc(iris, where(is.numeric)),
  )
  expect_identical(
    select_loc(iris, function(x) is.numeric(x)),
    select_loc(iris, where(is.numeric)),
  )
})

test_that("predicates have access to the full data", {
  p <- function(x) is.numeric(x) && mean(x) > 5
  expect_identical(select_loc(iris, where(p)), c(Sepal.Length = 1L))
})

test_that("unary `-` is alias for `!`", {
  expect_identical(select_loc(mtcars, -(cyl:carb)), c(mpg = 1L))
})

test_that("empty inputs return empty indices", {
  expect_identical(select_loc(mtcars, int()), named(int()))
  expect_identical(select_loc(mtcars, !!int()), named(int()))
})

test_that("indices are returned in order of evaluation", {
  expect_identical(select_loc(mtcars, cyl | mpg), c(cyl = 2L, mpg = 1L))
  expect_identical(select_loc(mtcars, c(cyl | mpg)), c(cyl = 2L, mpg = 1L))
})

test_that("0 is ignored", {
  expect_identical(select_loc(mtcars, 0), named(int()))
  expect_identical(select_loc(mtcars, identity(0)), named(int()))
  expect_identical(select_loc(mtcars, 0L | 0L), named(int()))
  expect_identical(select_loc(mtcars, c(0L, -1L)), named(int()))
})

test_that("negative indices are disallowed", {
  expect_error(select_loc(mtcars, identity(c(-1, 1))), "negative")
  expect_error(select_loc(mtcars, !!c(-1, 1)), "negative")
  expect_error(select_loc(mtcars, cyl | !!c(-1, 1)), "negative")
})

test_that("unique elements are returned", {
  x <- list(a = 1L, b = 2L)
  expect_identical(select_loc(x, !!c(1L, 1L)), c(a = 1L))
  expect_identical(select_loc(x, !!c(1L, foo = 1L)), c(foo = 1L))
  expect_identical(select_loc(x, !!c(foo = 1L, 1L)), c(foo = 1L))
  expect_identical(select_loc(x, !!c(foo = 1L, 1L, bar = 1L)), c(foo = 1L, bar = 1L))
})

test_that("selections provide informative errors", {
  expect_snapshot(error = TRUE, {
    "Foreign errors during evaluation"
    select_loc(iris, eval_tidy(foobar))
  })
})

test_that("can select with .data pronoun (#2715)", {
  withr::local_options(lifecycle_verbosity = "quiet")

  expect_identical(select_loc(c(foo = "foo"), .data$foo), c(foo = 1L))
  expect_identical(select_loc(c(foo = "foo"), .data[["foo"]]), c(foo = 1L))
  expect_identical(select_loc(letters2, .data$a:.data$b), c(a = 1L, b = 2L))
  expect_identical(select_loc(letters2, .data[["a"]]:.data[["b"]]), c(a = 1L, b = 2L))
})

test_that("use of .data is deprecated", {
  x <- list(a = 1, b = 2, c = 3)
  var <- "a"
  expect_snapshot(x <- select_loc(x, .data$a))
  expect_snapshot(x <- select_loc(x, .data[[var]]))
})

test_that(".data in env-expression has the lexical definition", {
  quo <- local({
    .data <- mtcars
    quo({ stopifnot(identical(.data, mtcars)); NULL})
  })
  expect_error(select_loc(mtcars, !!quo), regexp = NA)
})

test_that("binary `/` is short for set difference", {
  expect_identical(
    select_loc(iris, starts_with("Sepal") / ends_with("Width")),
    select_loc(iris, c(starts_with("Sepal"), -ends_with("Width")))
  )
})

test_that("can select names with unrepresentable characters", {
  skip_if_not_installed("rlang", "0.4.2.9000")

  # R now emits a warning when converting to symbol. Since Windows
  # gained UTF-8 support, supporting unrepresentable characters is no
  # longer necessary.
  suppressWarnings(
    withr::with_locale(c(LC_CTYPE = "C"), {
      name <- "\u4e2d"
      tbl <- setNames(data.frame(a = 1), name)
      expect_identical(
        select_loc(tbl, !!sym(name)),
        set_names(1L, name)
      )
    })
  )
})

test_that("`-1:-2` is syntax for `-(1:2)` for compatibility", {
  expect_identical(
    select_loc(iris, -1:-2),
    select_loc(iris, -(1:2))
  )
  expect_identical(
    select_loc(iris, -Sepal.Length:-Sepal.Width),
    select_loc(iris, -(Sepal.Length:Sepal.Width))
  )
})

test_that("eval_sym() doesn't look for functions in the context", {
  foo <- is.numeric
  expect_error(select_loc(iris, foo), class = "vctrs_error_subscript_oob")
  expect_error(select_loc(iris, data), class = "vctrs_error_subscript_oob")
})

test_that("eval_sym() still supports predicate functions starting with `is`", {
  local_options(lifecycle_verbosity = "quiet")
  expect_identical(select_loc(iris, is_integer), select_loc(iris, where(is_integer)))
  expect_identical(select_loc(iris, is.numeric), select_loc(iris, where(is.numeric)))
  expect_identical(select_loc(iris, isTRUE), select_loc(iris, where(isTRUE)))
})

test_that("eval_walk() has informative messages", {
  expect_snapshot({
    "Using a predicate without where() warns"
    invisible(select_loc(iris, is_integer))
    invisible(select_loc(iris, is.numeric))
    invisible(select_loc(iris, isTRUE))

    "Warning is not repeated"
    invisible(select_loc(iris, is_integer))

    "formula shorthand must be wrapped"
    (expect_error(select_loc(mtcars, ~ is.numeric(.x))))
    (expect_error(select_loc(mtcars, ~ is.numeric(.x) || is.factor(.x) || is.character(.x))))
    (expect_error(select_loc(mtcars, ~ is.numeric(.x) || is.factor(.x) || is.character(.x) ||
                                       is.numeric(.x) || is.factor(.x) || is.character(.x))))

    (expect_error(select_loc(mtcars, .data$"foo")))
  })
})

test_that("can forbid empty selection", {
  expect_snapshot(error = TRUE, {
    ensure_named(integer(), allow_empty = FALSE)
    ensure_named(integer(), allow_empty = FALSE, allow_rename = FALSE)
  })
})

test_that("can make empty selection with allow_rename = FALSE", {
  expect_equal(
    select_loc(mtcars, character(), allow_rename = FALSE),
    set_names(integer(0))
  )
  expect_equal(
    select_loc(mtcars, c(cyl, am), allow_rename = FALSE),
    c(cyl = 2L, am = 9L)
  )
})
lionel-/selectr documentation built on March 14, 2024, 10:04 p.m.