test_that("composed functions are applied right to left by default", {
  expect_identical(!is.null(4), compose(`!`, is.null)(4))

  set.seed(1)
  x <- sample(1:4, 100, replace = TRUE)
  expect_identical(unname(sort(table(x))), compose(unname, sort, table)(x))
})

test_that("composed functions are applied in reverse order if .dir is supplied", {
  expect_identical(compose(~ .x + 100, ~ .x * 2, .dir = "forward")(2), 204)
})

test_that("compose supports formulas", {
  round_mean <- compose(~ .x * 100, ~ round(.x, 2), ~ mean(.x, na.rm = TRUE))

  expect_s3_class(round_mean, "purrr_function_compose")
  expect_identical(round_mean(1:100), round(mean(1:100, na.rm = TRUE), 2) * 100)
})

test_that("compose() supports character vectors", {
  fn <- local({
    foobar <- function(x) paste(x, "baz")
    compose("foobar", "foobar")
  })
  expect_identical(fn("quux"), "quux baz baz")
})

test_that("can splice lists of functions", {
  fns <- list(
    ~ paste(.x, "a"),
    ~ paste(.x, "b")
  )
  fn <- compose(!!!fns)
  expect_identical(fn("c"), "c b a")
})

test_that("composed function has formals of first function called", {
  fn <- function(x, y = 1) NULL
  expect_identical(formals(compose(identity, fn)), formals(fn))
})

test_that("can compose primitive functions", {
  expect_identical(compose(is.character, as.character)(3), TRUE)
  expect_identical(compose(`-`, `/`)(4, 2), -2)
})

test_that("composed function prints informatively", {
  fn1 <- set_env(function(x) x + 1, global_env())
  fn2 <- set_env(function(x) x / 1, global_env())
  expect_snapshot({
    "Single input"
    compose(fn1)

    "Multiple inputs"
    compose(fn1, fn2)
  })
})

test_that("compose() with 0 inputs returns the identity", {
  expect_identical(compose()(mtcars), mtcars)
})

test_that("compose() with 1 input is a noop", {
  expect_identical(compose(toupper)(letters), toupper(letters))
})

test_that("compose() works with generic functions (#629)", {
  purrr__gen <- function(x) UseMethod("purrr__gen")

  local({
    purrr__gen.default <- function(x) x + 1
    expect_identical(compose(~ purrr__gen(.x))(0), 1)
    expect_identical(compose(~ purrr__gen(.x), ~ purrr__gen(.x))(0), 2)

    expect_identical(compose(purrr__gen)(0), 1)
    expect_identical(compose(purrr__gen, purrr__gen)(0), 2)
  })
})

test_that("compose() works with generic functions (#639)", {
  n_unique <- purrr::compose(length, unique)
  expect_identical(n_unique(iris$Species), 3L)
})

test_that("compose() works with argument matching functions", {
  # They inspect their dynamic context via sys.function()
  fn <- function(x = c("foo", "bar")) match.arg(x)
  expect_identical(compose(fn)("f"), "foo")
  expect_identical(compose(fn, fn)("f"), "foo")
})

test_that("compose() works with non-local exits", {
  fn <- function(x) return(x)
  expect_identical(compose(fn)("foo"), "foo")
  expect_identical(compose(fn, fn)("foo"), "foo")
  expect_identical(
    compose(~ return(paste(.x, "foo")), ~ return("bar"))(),
    "bar foo"
  )
})

test_that("compose() preserves lexical environment", {
  fn <- local({
    `_foo` <- "foo"
    function(...) `_foo`
  })
  expect_identical(compose(fn)(), "foo")
  expect_identical(compose(fn, fn)(), "foo")
})

test_that("compose() can take dots from multiple environments", {
  f <- function(...) {
    `_foo` <- "foo"
    g(`_foo`, ...)
  }
  g <- function(...) {
    `_bar` <- "bar"
    h(`_bar`, ...)
  }
  h <- function(...) {
    `_baz` <- "baz"
    fn(`_baz`, ...)
  }
  `_quux` <- "quux"

  # By value
  fn <- compose(function(...) c(...))
  expect_identical(f(`_quux`), c("baz", "bar", "foo", "quux"))

  # By expression (base)
  fn <- compose(function(...) substitute(...()))
  expect_identical(
    f(`_quux`),
    as.pairlist(exprs(`_baz`, `_bar`, `_foo`, `_quux`))
  )

  # By expression (rlang)
  fn <- compose(function(...) enquos(...))
  quos <- f(`_quux`)

  frame <- current_env()
  expect_true(is_reference(quo_get_env(quos[[4]]), frame))
  expect_false(is_reference(quo_get_env(quos[[3]]), frame))

  expect_identical(
    unname(map_chr(quos, as_name)),
    c("_baz", "_bar", "_foo", "_quux")
  )
})
