tests/testthat/test-tf-ggplot-basic.R

# Basic tf_ggplot functionality tests
# These tests focus on core features that must work before more advanced functionality

test_that("tf_ggplot basic constructor works", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 2, n_points = 5)

  # Basic constructor without aesthetics
  p1 <- tf_ggplot(data)
  expect_s3_class(p1, c("tf_ggplot", "ggplot"))
  expect_identical(p1$data, data)

  # Constructor with aesthetics
  p2 <- tf_ggplot(data, aes(tf = func, color = group))
  expect_s3_class(p2, c("tf_ggplot", "ggplot"))
  expect_equal(length(p2$mapping), 2)

  # Constructor with custom arg
  custom_arg <- c(0, 0.5, 1)
  p3 <- tf_ggplot(data, arg = custom_arg)
  expect_equal(attr(p3, "tf_arg"), custom_arg)
})

test_that("basic tf aesthetic transformation works", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 2, n_points = 5)

  # Single tf aesthetic
  p <- tf_ggplot(data, aes(tf = func)) + geom_line()

  # Should still be tf_ggplot until finalized
  expect_s3_class(p, "ggplot")
  expect_true(inherits(p, "tf_ggplot"))

  # Check plot builds successfully
  built <- ggplot_build(p)
  expect_s3_class(built, "ggplot_built")

  # Should have correct number of groups (one per function)
  expect_equal(count_plot_groups(p), 2)

  # Should have x and y mappings
  expect_true(check_plot_mappings(p, c("x", "y", "group")))
})

test_that("tf aesthetic parsing identifies tf columns correctly", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  # Test different tf aesthetic patterns
  mapping1 <- aes(tf = func)
  parsed1 <- parse_tf_aesthetics(mapping1)
  expect_equal(names(parsed1$tf_aes), "tf")
  expect_equal(length(parsed1$regular_aes), 0)

  mapping2 <- aes(tf = func, color = group)
  parsed2 <- parse_tf_aesthetics(mapping2)
  expect_equal(names(parsed2$tf_aes), "tf")
  expect_equal(length(parsed2$regular_aes), 1)

  mapping3 <- aes(tf_x = func1, tf_y = func2)
  parsed3 <- parse_tf_aesthetics(mapping3)
  expect_equal(sort(names(parsed3$tf_aes)), c("tf_x", "tf_y"))
  expect_equal(length(parsed3$regular_aes), 0)

  # Test no tf aesthetics
  mapping4 <- aes(x = time, y = value)
  parsed4 <- parse_tf_aesthetics(mapping4)
  expect_equal(length(parsed4$tf_aes), 0)
  expect_equal(length(parsed4$regular_aes), 2)
})

test_that("data transformation creates correct structure via ggplot_build", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- data.frame(
    id = 1:2,
    group = factor(c("A", "B"))
  )
  data$func <- tf_rgp(2, arg = seq(0, 1, length.out = 5))

  p <- tf_ggplot(data, aes(tf = func)) + geom_line()
  built <- ggplot_build(p)
  plot_data <- built$data[[1]]

  # Check dimensions (2 functions × 5 points)
  expect_equal(nrow(plot_data), 10)
  expect_equal(length(unique(plot_data$group)), 2)
  expect_equal(length(unique(plot_data$x)), 5)
})

test_that("geom_line integration produces correct plot structure", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 3, n_points = 6)

  p <- tf_ggplot(data, aes(tf = func, color = group)) +
    geom_line()

  # Build plot and check structure
  built <- ggplot_build(p)
  plot_data <- built$data[[1]]

  # Should have 3 groups (one per function)
  expect_equal(length(unique(plot_data$group)), 3)

  # Should have 6 unique x values (evaluation points)
  expect_equal(length(unique(plot_data$x)), 6)

  # Should have 18 total rows (3 functions × 6 points)
  expect_equal(nrow(plot_data), 18)

  # Should have color aesthetic preserved
  expect_true("colour" %in% names(plot_data))
})

test_that("error handling for invalid tf aesthetics", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  # Data without tf columns
  data <- data.frame(
    id = 1:3,
    regular_col = rnorm(3)
  )

  # Should error when tf aesthetic references non-tf column
  expect_error(
    {
      p <- tf_ggplot(data, aes(tf = regular_col)) +
        geom_line()
      ggplot_build(p) # Trigger the validation
    },
    "tf.*object|must be.*tf"
  )

  # Should error when tf aesthetic references non-existent column
  expect_error(
    {
      p <- tf_ggplot(data, aes(tf = nonexistent)) +
        geom_line()
      ggplot_build(p) # Trigger the validation
    },
    "object.*not found|not found"
  )
})

test_that("custom arg parameter works correctly", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 2, n_points = 11)

  # Use coarser evaluation grid (numeric vector)
  custom_arg <- seq(0, 1, length.out = 5)
  p <- tf_ggplot(data, aes(tf = func), arg = custom_arg) +
    geom_line()

  # Check that custom arg is used
  x_values <- get_plot_x_values(p)
  expect_equal(x_values, custom_arg)
  expect_equal(length(x_values), 5) # Not original 11

  # Check total rows (2 functions × 5 points)
  expect_true(check_plot_nrows(p, 10))
})

test_that("integer arg sets grid length", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 2, n_points = 21)

  # Integer arg = grid length
  p <- tf_ggplot(data, aes(tf = func), arg = 5L) + geom_line()
  x_values <- get_plot_x_values(p)
  expect_equal(length(x_values), 5)
  expect_equal(range(x_values), c(0, 1)) # Should span the tf domain

  # Check total rows (2 functions × 5 points)
  expect_true(check_plot_nrows(p, 10))

  # Validation: arg < 2 should error
  expect_error(tf_ggplot(data, arg = 1L), "grid length must be >= 2")
  expect_error(tf_ggplot(data, arg = NA_real_), "numeric")
})

test_that("grouping variables are preserved in transformation", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 4, n_points = 5)
  data$treatment <- factor(c("A", "A", "B", "B"))
  data$subject <- factor(1:4)

  p <- tf_ggplot(data, aes(tf = func, color = treatment)) +
    geom_line()
  built <- ggplot_build(p)
  plot_data <- built$data[[1]]

  # Note: unmapped variables are not preserved in final plot data (this is normal ggplot2 behavior)
  # But the color aesthetic should reflect treatment mapping
  expect_true("colour" %in% names(plot_data))

  # Should have correct number of colors (2 treatment levels)
  expect_equal(length(unique(plot_data$colour)), 2)

  # Should have consistent color within each function
  color_by_group <- split(plot_data$colour, plot_data$group)
  consistent_colors <- all(sapply(
    color_by_group,
    function(x) length(unique(x)) == 1
  ))
  expect_true(consistent_colors)
})

Try the tidyfun package in your browser

Any scripts or data that you put into this service are public.

tidyfun documentation built on April 24, 2026, 5:06 p.m.