skip_on_cran() # This test suite is long-running (on cran) and is skipped

test_that("dollar subsetting the theme does no partial matching", {
  t <- theme(foobar = 12)
  expect_null(t$foo)
  expect_equal(t$foobar, 12)
})

test_that("theme argument splicing works", {
  l <- list(a = 10, b = "c", d = c("foo", "bar"))
  test <- theme(!!!l)
  ref  <- theme(a = 10, b = "c", d = c("foo", "bar"))
  expect_equal(test, ref)
})

test_that("modifying theme element properties with + operator works", {

  # Changing a "leaf node" works
  t <- theme_grey() + theme(axis.title.x = element_text(colour = 'red', margin = margin()))
  expect_identical(t$axis.title.x, element_text(colour = 'red', margin = margin(), vjust = 1))
  # Make sure the theme class didn't change or get dropped
  expect_s7_class(t, class_theme)
  # Make sure the element class didn't change or get dropped
  expect_s7_class(t$axis.title.x, element)
  expect_s7_class(t$axis.title.x, element_text)

  # Modifying an intermediate node works
  t <- theme_grey() + theme(axis.title = element_text(colour = 'red'))
  expect_identical(t$axis.title, element_text(colour = 'red'))

  # Modifying a root node changes only the specified properties
  t <- theme_grey() + theme(text = element_text(colour = 'red'))
  expect_identical(t$text@colour, 'red')
  expect_identical(t$text@family, theme_grey()$text@family)
  expect_identical(t$text@face,   theme_grey()$text@face)
  expect_identical(t$text@size,   theme_grey()$text@size)
  # Descendent is unchanged
  expect_identical(t$axis.title.x, theme_grey()$axis.title.x)

  # Adding element_blank replaces element
  t <- theme_grey() + theme(axis.text.y = element_blank())
  expect_identical(t$axis.text.y, element_blank())

  # Adding a non-blank element to an element_blank() replaces it
  t <- t + theme(axis.text.y = element_text(colour = 'red'))
  expect_identical(t$axis.text.y, element_text(colour = 'red'))

  # Adding empty theme() has no effect
  t <- theme_grey() + theme()
  expect_identical(t, theme_grey())

  expect_snapshot(
    theme_grey() + "asdf",
    error = TRUE,
    variant = substr(as.character(getRversion()), start = 1, stop = 3)
  )
})

test_that("set_theme() resets theme to default when called with no arguments", {
  theme_set(theme_void())
  set_theme()
  expect_identical(theme_get(), theme_grey())
})

test_that("adding theme object to ggplot object with + operator works", {
  ## test with complete theme
  p <- ggplot(data.frame(x = 1:3), aes(x, x)) + geom_point() + theme_grey()
  p <- p + theme(axis.title = element_text(size = 20))
  expect_true(p@theme$axis.title@size == 20)

  # Should update specified properties, but not reset other properties
  p <- p + theme(text = element_text(colour = 'red'))
  expect_true(p@theme$text@colour == 'red')
  tt <- theme_grey()$text
  tt@colour <- 'red'
  expect_true(tt@inherit.blank)
  tt@inherit.blank <- FALSE
  expect_identical(p@theme$text, tt)

  ## test without complete theme
  p <- ggplot(data.frame(x = 1:3), aes(x, x)) + geom_point()
  p <- p + theme(axis.title = element_text(size = 20))
  expect_true(p@theme$axis.title@size == 20)

  # Should update specified properties, but not reset other properties
  p <- p + theme(text = element_text(colour = 'red'))
  expect_true(p@theme$text@colour == 'red')
  expect_null(p@theme$text@family)
  expect_null(p@theme$text@face)
  expect_null(p@theme$text@size)
  expect_null(p@theme$text@hjust)
  expect_null(p@theme$text@vjust)
  expect_null(p@theme$text@angle)
  expect_null(p@theme$text@lineheight)
  expect_null(p@theme$text@margin)
  expect_null(p@theme$text@debug)

  ## stepwise addition of partial themes is identical to one-step addition
  p <- ggplot(data.frame(x = 1:3), aes(x, x)) + geom_point()
  p1 <- p + theme_light() +
    theme(axis.line.x = element_line(color = "blue")) +
    theme(axis.ticks.x = element_line(color = "red"))

  p2 <- p + theme_light() +
    theme(axis.line.x = element_line(color = "blue"),
          axis.ticks.x = element_line(color = "red"))

  expect_identical(p1@theme, p2@theme)
})

test_that("replacing theme elements with %+replace% operator works", {
  # Changing a "leaf node" works
  t <- theme_grey() %+replace% theme(axis.title.x = element_text(colour = 'red'))
  expect_identical(t$axis.title.x, element_text(colour = 'red'))
  # Make sure the class didn't change or get dropped
  expect_s7_class(t, class_theme)

  # Changing an intermediate node works
  t <- theme_grey() %+replace% theme(axis.title = element_text(colour = 'red'))
  expect_identical(t$axis.title, element_text(colour = 'red'))
  # Descendent is unchanged
  expect_identical(t$axis.title.x, theme_grey()$axis.title.x)

  # Adding empty theme() has no effect
  t <- theme_grey() %+replace% theme()
  expect_identical(t, theme_grey())
})

test_that("calculating theme element inheritance works", {
  t <- theme_grey() + theme(axis.title = element_text(colour = 'red'))

  # Check that properties are passed along from axis.title to axis.title.x
  e <- calc_element('axis.title.x', t)
  expect_identical(e@colour, 'red')
  expect_false(is.null(e@family))
  expect_false(is.null(e@face))
  expect_false(is.null(e@size))

  # Check that rel() works for relative sizing, and is applied at each level
  t <- theme_grey(base_size = 12) +
    theme(axis.title   = element_text(size = rel(0.5))) +
    theme(axis.title.x = element_text(size = rel(0.5)))
  e <- calc_element('axis.title', t)
  expect_identical(e@size, 6)
  ex <- calc_element('axis.title.x', t)
  expect_identical(ex@size, 3)

  # Check that a theme_blank in a parent node gets passed along to children
  t <- theme_grey() + theme(text = element_blank())
  expect_identical(calc_element('axis.title.x', t), element_blank())

  # Check that inheritance from derived class works
  element_dummyrect <- S7::new_class(
    "element_dummyrect", parent = element_rect,
    properties = list(dummy = S7::class_any)
  )

  e <- calc_element(
    "panel.background",
    theme(
      rect = element_rect(fill = "white", colour = "black", linewidth = 0.5, linetype = 1, linejoin = "round"),
      panel.background = element_dummyrect(dummy = 5),
      complete = TRUE # need to prevent pulling in default theme
    )
  )

  expect_identical(
    e,
    element_dummyrect(
      fill = "white", colour = "black", dummy = 5, linewidth = 0.5, linetype = 1, linejoin = "round",
      inherit.blank = TRUE # this is true because we're requesting a complete theme
    )
  )

  # Check that blank elements are skipped in inheritance tree if and only if elements
  # don't inherit from blank.
  t <- theme_gray() +
    theme(
      strip.text = element_blank(),
      strip.text.x = element_text() # inherit.blank = FALSE is default
    )
  e1 <- calc_element("strip.text.x", t)
  e2 <- calc_element("text", t)
  e2@inherit.blank <- FALSE # b/c inherit.blank = TRUE for complete themes
  expect_identical(e1, e2)

  theme <- theme_gray() +
    theme(strip.text = element_blank(), strip.text.x = element_text(inherit.blank = TRUE))
  e1 <- ggplot2:::calc_element("strip.text.x", theme)
  e2 <- ggplot2:::calc_element("strip.text", theme)
  expect_identical(e1, e2)

  # Check that rel units are computed appropriately
  theme <- theme_gray() +
    theme(axis.ticks.length = unit(1, "cm"),
          axis.ticks.length.x = rel(0.5),
          axis.ticks.length.x.bottom = rel(4))

  expect_equal(calc_element("axis.ticks.length.y.left", theme), unit(1, "cm"))
  expect_equal(calc_element("axis.ticks.length.x.top", theme), unit(1, "cm") * 0.5)
  expect_equal(calc_element("axis.ticks.length.x.bottom", theme), unit(1, "cm") * 0.5 * 4)
})

test_that("complete and non-complete themes interact correctly with each other", {
  # The 'complete' attribute of t1 + t2 is the OR of their 'complete' attributes.

  # But for _element properties_, the one on the right modifies the one on the left.
  t <- theme_bw() + theme(text = element_text(colour = 'red'))
  expect_true(attr(t, "complete"))
  expect_equal(t$text@colour, 'red')

  # A complete theme object (like theme_bw) always trumps a non-complete theme object
  t <- theme(text = element_text(colour = 'red')) + theme_bw()
  expect_true(attr(t, "complete"))
  expect_equal(t$text@colour, theme_bw()$text@colour)

  # Adding two non-complete themes: the one on the right modifies the one on the left.
  t <- theme(text = element_text(colour = 'blue')) +
    theme(text = element_text(colour = 'red'))
  expect_false(attr(t, "complete"))
  expect_equal(t$text@colour, 'red')
})

test_that("complete and non-complete themes interact correctly with ggplot objects", {
  base <- ggplot(data.frame(x = 1:3), aes(x, x)) + geom_point()

  # Check that adding two theme successive theme objects to a ggplot object
  # works like adding the two theme object to each other
  p <- ggplot_build(base + theme_bw() + theme(text = element_text(colour = 'red')))
  expect_true(attr(p@plot@theme, "complete"))

  # Compare the theme objects, after sorting the items, because item order can differ
  pt <- p@plot@theme
  tt <- theme_bw() + theme(text = element_text(colour = 'red'))
  pt <- pt[order(names(pt))]
  tt <- tt[order(names(tt))]
  expect_identical(pt, tt)

  p <- ggplot_build(base + theme(text = element_text(colour = 'red')) + theme_bw())
  expect_true(attr(p@plot@theme, "complete"))
  # Compare the theme objects, after sorting the items, because item order can differ
  pt <- p@plot@theme
  tt <- theme(text = element_text(colour = 'red')) + theme_bw()
  pt <- pt[order(names(pt))]
  tt <- tt[order(names(tt))]
  expect_identical(pt, tt)

  p <- ggplot_build(base + theme(text = element_text(colour = 'red', face = 'italic')))
  expect_equal(p@plot@theme$text@colour, "red")
  expect_equal(p@plot@theme$text@face, "italic")

  p <- ggplot_build(base +
    theme(text = element_text(colour = 'red')) +
    theme(text = element_text(face = 'italic')))
  expect_equal(p@plot@theme$text@colour, "red")
  expect_equal(p@plot@theme$text@face, "italic")
})

test_that("theme(validate=FALSE) means do not check_element", {
  p <- ggplot(data.frame(x = 1:3), aes(x, x)) + geom_point()
  bw <- p + theme_bw()
  red.text <- theme(text = element_text(colour = "red"))
  bw.before <- bw + theme(animint.width = 500, validate = FALSE)
  expect_equal(bw.before@theme$animint.width, 500)

  bw.after <- p + theme(animint.width = 500, validate = FALSE) + theme_bw()
  expect_null(bw.after@theme$animint.width)

  red.after <- p + theme(animint.width = 500, validate = FALSE) + red.text
  expect_equal(red.after@theme$animint.width, 500)

  red.before <- p + red.text + theme(animint.width = 500, validate = FALSE)
  expect_equal(red.before@theme$animint.width, 500)
})

test_that("theme validation happens at build stage", {
  # adding a non-valid theme element to a theme is no problem
  expect_silent(theme_gray() + theme(text = 0))

  # the error occurs when we try to render the plot
  p <- ggplot() + theme(text = 0)
  expect_snapshot_error(print(p))

  # without validation, the error occurs when the element is accessed
  p <- ggplot() + theme(text = 0, validate = FALSE)
  expect_snapshot_error(print(p))
})

test_that("incorrect theme specifications throw meaningful errors", {
  expect_snapshot_error(add_theme(theme_grey(), theme(line = element_rect())))
  expect_snapshot_error(calc_element("line", theme(line = element_rect())))
  register_theme_elements(element_tree = list(test = el_def(element_rect)))
  expect_snapshot_error(calc_element("test", theme_gray() + theme(test = element_rect())))
  expect_snapshot_error(set_theme("foo"))
  reset_theme_settings()
})

test_that("element tree can be modified", {
  # we cannot add a new theme element without modifying the element tree
  p <- ggplot() + theme(blablabla = element_text(colour = "red"))
  expect_snapshot_warning(print(p))

  register_theme_elements(
    element_tree = list(blablabla = el_def("character", "text"))
  )
  expect_snapshot_error(ggplotGrob(p))

  register_theme_elements(
    element_tree = list(blablabla = el_def("unit", "text"))
  )
  expect_snapshot_error(ggplotGrob(p))

  # things work once we add a new element to the element tree
  register_theme_elements(
    element_tree = list(blablabla = el_def(element_text, "text"))
  )
  expect_silent(ggplotGrob(p))

  p1 <- ggplot() + theme(blablabla = element_line())
  expect_snapshot_error(ggplotGrob(p1))

  # Expect errors for invalid element trees
  expect_snapshot_error(
    register_theme_elements(element_tree = list(el_def("rect"), el_def("line")))
  )
  expect_snapshot_error(
    register_theme_elements(element_tree = list(foo = "bar"))
  )
  expect_snapshot_error(
    register_theme_elements(element_tree = list(foo = el_def(inherit = "foo")))
  )

  # inheritance and final calculation of novel element works
  final_theme <- ggplot2:::plot_theme(p, theme_gray())
  e1 <- calc_element("blablabla", final_theme)
  e2 <- calc_element("text", final_theme)
  expect_identical(e1@family, e2@family)
  expect_identical(e1@face, e2@face)
  expect_identical(e1@size, e2@size)
  expect_identical(e1@lineheight, e2@lineheight)
  expect_identical(e1@colour, "red") # not inherited from element_text

  # existing elements can be overwritten
  ed <- el_def(element_rect, "rect")
  register_theme_elements(
    element_tree = list(axis.title = ed)
  )
  expect_identical(get_element_tree()$axis.title, ed)

  reset_theme_settings() # revert back to defaults
})

test_that("all elements in complete themes have inherit.blank=TRUE", {
  inherit_blanks <- function(theme) {
    all(vapply(
      theme, try_prop,
      name = "inherit.blank", default = TRUE,
      logical(1)
    ))
  }
  expect_true(inherit_blanks(theme_grey()))
  expect_true(inherit_blanks(theme_bw()))
  expect_true(inherit_blanks(theme_classic()))
  expect_true(inherit_blanks(theme_dark()))
  expect_true(inherit_blanks(theme_light()))
  expect_true(inherit_blanks(theme_linedraw()))
  expect_true(inherit_blanks(theme_minimal()))
  expect_true(inherit_blanks(theme_void()))
})

test_that("elements can be merged", {
  text_base <- element_text(colour = "red", size = 10)
  expect_equal(
    merge_element(element_text(colour = "blue"), text_base),
    element_text(colour = "blue", size = 10)
  )
  rect_base <- element_rect(colour = "red", linewidth = 10)
  expect_equal(
    merge_element(element_rect(colour = "blue"), rect_base),
    element_rect(colour = "blue", linewidth = 10)
  )
  line_base <- element_line(colour = "red", linewidth = 10)
  expect_equal(
    merge_element(element_line(colour = "blue"), line_base),
    element_line(colour = "blue", linewidth = 10)
  )
  expect_snapshot(merge_element(text_base, rect_base), error = TRUE)
})

test_that("theme elements that don't inherit from element can be combined", {
  expect_identical(combine_elements(1, NULL), 1)
  expect_identical(combine_elements(NULL, 1), 1)
  expect_identical(combine_elements(1, 0), 1)
})

test_that("complete plot themes shouldn't inherit from default", {
  default_theme <- theme_gray() + theme(axis.text.x = element_text(colour = "red"))
  base <- ggplot(data.frame(x = 1), aes(x, x)) + geom_point()

  ptheme <- plot_theme(base + theme(axis.text.x = element_text(colour = "blue")), default_theme)
  expect_equal(ptheme$axis.text.x@colour, "blue")

  ptheme <- plot_theme(base + theme_void(), default_theme)
  expect_null(ptheme$axis.text.x)
})

test_that("current theme can be updated with new elements", {
  old <- set_theme(theme_grey())

  b1 <- ggplot() + theme_grey()
  b2 <- ggplot()

  # works for root element
  expect_identical(
    calc_element("text", plot_theme(b1)),
    calc_element("text", plot_theme(b2))
  )

  # works for derived element
  expect_identical(
    calc_element("axis.text.x", plot_theme(b1)),
    calc_element("axis.text.x", plot_theme(b2))
  )

  # theme calculation for nonexisting element returns NULL
  expect_null(calc_element("abcde", plot_theme(b1)))

  # element tree gets merged properly
  register_theme_elements(
    abcde = element_text(color = "blue", hjust = 0, vjust = 1),
    element_tree = list(abcde = el_def(element_text, "text"))
  )

  e1 <- calc_element("abcde", plot_theme(b2))
  e2 <- calc_element("text", plot_theme(b2))
  e2@colour <- "blue"
  e2@hjust <- 0
  e2@vjust <- 1
  expect_identical(e1, e2)

  reset_theme_settings()
  set_theme(old)
})

test_that("titleGrob() and margins() work correctly", {
  # ascenders and descenders
  g1 <- titleGrob("aaaa", 0, 0, 0.5, 0.5) # lower-case letters, no ascenders or descenders
  g2 <- titleGrob("bbbb", 0, 0, 0.5, 0.5) # lower-case letters, no descenders
  g3 <- titleGrob("gggg", 0, 0, 0.5, 0.5) # lower-case letters, no ascenders
  g4 <- titleGrob("AAAA", 0, 0, 0.5, 0.5) # upper-case letters, no descenders

  expect_equal(height_cm(g1), height_cm(g2))
  expect_equal(height_cm(g1), height_cm(g3))
  expect_equal(height_cm(g1), height_cm(g4))

  # margins
  g5 <- titleGrob("aaaa", 0, 0, 0.5, 0.5, margin = margin(t = 1, r = 0, b = 0, l = 0, unit = "cm"), margin_x = TRUE, margin_y = TRUE)
  g6 <- titleGrob("aaaa", 0, 0, 0.5, 0.5, margin = margin(t = 0, r = 1, b = 0, l = 0, unit = "cm"), margin_x = TRUE, margin_y = TRUE)
  g7 <- titleGrob("aaaa", 0, 0, 0.5, 0.5, margin = margin(t = 0, r = 0, b = 1, l = 0, unit = "cm"), margin_x = TRUE, margin_y = TRUE)
  g8 <- titleGrob("aaaa", 0, 0, 0.5, 0.5, margin = margin(t = 0, r = 0, b = 0, l = 1, unit = "cm"), margin_x = TRUE, margin_y = TRUE)

  expect_equal(height_cm(g5), height_cm(g1) + 1)
  expect_equal(width_cm(g5), width_cm(g1))
  expect_equal(height_cm(g6), height_cm(g1))
  expect_equal(width_cm(g6), width_cm(g1) + 1)
  expect_equal(height_cm(g7), height_cm(g1) + 1)
  expect_equal(width_cm(g7), width_cm(g1))
  expect_equal(height_cm(g8), height_cm(g1))
  expect_equal(width_cm(g8), width_cm(g1) + 1)

  # no margins when set to false
  g9 <- titleGrob("aaaa", 0, 0, 0.5, 0.5, margin = margin(t = 1, r = 1, b = 1, l = 1, unit = "cm"), margin_x = FALSE, margin_y = TRUE)
  g10 <- titleGrob("aaaa", 0, 0, 0.5, 0.5, margin = margin(t = 1, r = 1, b = 1, l = 1, unit = "cm"), margin_x = TRUE, margin_y = FALSE)
  expect_equal(height_cm(g9), height_cm(g1) + 2)
  # when one of margin_x or margin_y is set to FALSE and the other to TRUE, then the dimension for FALSE turns into
  # length 1null.
  expect_equal(g9$widths, grid::unit(1, "null"))
  expect_equal(g10$heights, grid::unit(1, "null"))
  expect_equal(width_cm(g10), width_cm(g1) + 2)
})

test_that("margins() warn against wrong input lengths", {
  expect_snapshot(margin(c(1, 2), 3, 4, c(5, 6, 7)))
})

test_that("provided themes explicitly define all elements", {
  elements <- names(.element_tree)

  t <- theme_all_null()
  expect_true(all(names(t) %in% elements))
  expect_true(all(vapply(t, is.null, logical(1))))

  t <- theme_grey()
  expect_true(all(names(t) %in% elements))

  t <- theme_bw()
  expect_true(all(names(t) %in% elements))

  t <- theme_linedraw()
  expect_true(all(names(t) %in% elements))

  t <- theme_light()
  expect_true(all(names(t) %in% elements))

  t <- theme_dark()
  expect_true(all(names(t) %in% elements))

  t <- theme_minimal()
  expect_true(all(names(t) %in% elements))

  t <- theme_classic()
  expect_true(all(names(t) %in% elements))

  t <- theme_void()
  expect_true(all(names(t) %in% elements))

  t <- theme_test()
  expect_true(all(names(t) %in% elements))
})

test_that("Theme elements are checked during build", {
  p <- ggplot(mtcars) + geom_point(aes(disp, mpg)) + theme(plot.title.position = "test")
  expect_snapshot_error(ggplotGrob(p))

  p <- ggplot(mtcars) + geom_point(aes(disp, mpg)) + theme(plot.caption.position = "test")
  expect_snapshot_error(ggplotGrob(p))

  p <- ggplot(mtcars) + geom_point(aes(disp, mpg)) +
    theme(plot.tag.position = "test") + labs(tag = "test")
  expect_snapshot_error(ggplotGrob(p))
})

test_that("subtheme functions rename arguments as intended", {

  line <- element_line(colour = "red")
  rect <- element_rect(colour = "red")

  expect_equal(theme_sub_axis(ticks = line),        theme(axis.ticks = line))
  expect_equal(theme_sub_axis_x(ticks = line),      theme(axis.ticks.x = line))
  expect_equal(theme_sub_axis_y(ticks = line),      theme(axis.ticks.y = line))
  expect_equal(theme_sub_axis_top(ticks = line),    theme(axis.ticks.x.top = line))
  expect_equal(theme_sub_axis_bottom(ticks = line), theme(axis.ticks.x.bottom = line))
  expect_equal(theme_sub_axis_left(ticks = line),   theme(axis.ticks.y.left = line))
  expect_equal(theme_sub_axis_right(ticks = line),  theme(axis.ticks.y.right = line))
  expect_equal(theme_sub_legend(key = rect),        theme(legend.key = rect))
  expect_equal(theme_sub_panel(border = rect),      theme(panel.border = rect))
  expect_equal(theme_sub_plot(background = rect),   theme(plot.background = rect))
  expect_equal(theme_sub_strip(background = rect),  theme(strip.background = rect))

  # Test rejection of unknown theme elements
  expect_snapshot_warning(
    expect_equal(
      subtheme(list(foo = 1, bar = 2, axis.line = line)),
      theme(axis.line = line)
    )
  )
})

test_that("element_text throws appropriate conditions", {
  expect_snapshot_warning(
    element_text(colour = c("red", "blue"))
  )
  expect_snapshot_warning(
    element_text(margin = unit(1, "cm"))
  )
  expect_snapshot(
    element_text(margin = 5),
    error = TRUE
  )
  expect_snapshot(
    element_text(colour = sqrt(2)),
    error = TRUE
  )

  # Some absurd case found in reverse dependency check where
  # labs(y = element_blank()) for some reason
  el <- theme_get()$text
  expect_snapshot(
    element_grob(el, label = element_blank())
  )
})

test_that("Theme validation behaves as expected", {
  tree <- get_element_tree()
  expect_silent(check_element(1,  "aspect.ratio", tree))
  expect_silent(check_element(1L, "aspect.ratio", tree))
  expect_snapshot_error(check_element("A", "aspect.ratio", tree))
})

test_that("Element subclasses are inherited", {

  # `rich` is subclass of `poor`
  poor <- element_line(colour = "red", linetype = 3)
  rich <- element_line(linetype = 2, linewidth = 2)
  class(rich) <- c("element_rich", class(rich))

  # `poor` should acquire `rich`
  test <- combine_elements(poor, rich)
  expect_s3_class(test, "element_rich")
  expect_equal(
    S7::props(test)[c("colour", "linetype", "linewidth")],
    list(colour = "red", linetype = 3, linewidth = 2)
  )

  # `rich` should stay `rich`
  test <- combine_elements(rich, poor)
  expect_s3_class(test, "element_rich")
  expect_equal(
    S7::props(test)[c("colour", "linetype", "linewidth")],
    list(colour = "red", linetype = 2, linewidth = 2)
  )

  # `sibling` is not strict subclass of `rich`
  sibling <- poor
  class(sibling) <- c("element_sibling", class(sibling))

  # `sibling` should stay `sibling`
  test <- combine_elements(sibling, rich)
  expect_s3_class(test, "element_sibling")
  expect_equal(
    S7::props(test)[c("colour", "linetype", "linewidth")],
    list(colour = "red", linetype = 3, linewidth = 2)
  )

  # `rich` should stay `rich`
  test <- combine_elements(rich, sibling)
  expect_s3_class(test, "element_rich")
  expect_equal(
    S7::props(test)[c("colour", "linetype", "linewidth")],
    list(colour = "red", linetype = 2, linewidth = 2)
  )
})

test_that("Minor tick length supports biparental inheritance", {
  my_theme <- theme_gray() + theme(
    axis.ticks.length = unit(1, "cm"),
    axis.ticks.length.y.left = unit(1, "pt"),
    axis.minor.ticks.length.y = unit(1, "inch"),
    axis.minor.ticks.length = rel(0.5)
  )
  expect_equal( # Inherits rel(0.5) from minor, 1cm from major
    calc_element("axis.minor.ticks.length.x.bottom", my_theme),
    unit(1, "cm") * 0.5
  )
  expect_equal( # Inherits 1inch directly from minor
    calc_element("axis.minor.ticks.length.y.left", my_theme),
    unit(1, "inch")
  )
})

test_that("header_family is passed on correctly", {

  td <- theme_dark(base_family = "x", header_family = "y")

  test <- calc_element("plot.title", td)
  expect_equal(test@family, "y")

  test <- calc_element("plot.subtitle", td)
  expect_equal(test@family, "x")
})

test_that("complete_theme completes a theme", {
  # `NULL` should match default
  gray <- theme_gray()
  new <- complete_theme(NULL, default = gray)
  expect_equal(S7::S7_data(new), S7::S7_data(gray))

  # Elements are propagated
  new <- complete_theme(theme(axis.line = element_line("red")), gray)
  expect_equal(new$axis.line@colour, "red")

  # Missing elements are filled in if default theme is incomplete
  new <- complete_theme(default = theme())
  expect_s3_class(new$axis.line, "ggplot2::element_blank")

  # Registered elements are included
  register_theme_elements(
    test = element_text(),
    element_tree = list(test = el_def(element_text, "text"))
  )
  new <- complete_theme(default = gray)
  expect_s3_class(new$test, "ggplot2::element_text")
  reset_theme_settings()
})

test_that("panel.widths and panel.heights works with free-space panels", {

  df <- data.frame(x = c(1, 1, 2, 1, 3), g = c("A", "B", "B", "C", "C"))

  p <- ggplotGrob(
    ggplot(df, aes(x, x)) +
      geom_point() +
      scale_x_continuous(expand = expansion(add = 1)) +
      facet_grid(~ g, scales = "free_x", space = "free_x") +
      theme(
        panel.widths = unit(11, "cm"),
        panel.spacing.x = unit(1, "cm")
      )
  )

  idx <- range(panel_cols(p)$l)
  expect_equal(as.numeric(p$widths[seq(idx[1], idx[2])]), c(2, 1, 3, 1, 4))

  p <- ggplotGrob(
    ggplot(df, aes(x, x)) +
      geom_point() +
      scale_y_continuous(expand = expansion(add = 1)) +
      facet_grid(g ~ ., scales = "free_y", space = "free_y") +
      theme(
        panel.heights = unit(11, "cm"),
        panel.spacing.y = unit(1, "cm")
      )
  )

  idx <- range(panel_rows(p)$t)
  expect_equal(as.numeric(p$heights[seq(idx[1], idx[2])]), c(2, 1, 3, 1, 4))

})

test_that("panel.withs and panel.heights preserve aspect ratios with single panels", {

  df <- data.frame(x = c(1, 2))

  p <- ggplotGrob(
    ggplot(df, aes(x, x)) +
      geom_point() +
      theme(
        aspect.ratio = 2,
        panel.heights = unit(10, "cm")
      )
  )

  width <- p$widths[panel_cols(p)$l]
  expect_equal(as.character(width), "5cm")

  p <- ggplotGrob(
    ggplot(df, aes(x, x)) +
      geom_point() +
      facet_wrap(~ x) + # This behaviour doesn't occur in multipanel plots.
      theme(
        aspect.ratio = 2,
        panel.heights = unit(10, "cm")
      )
  )

  width <- p$widths[panel_cols(p)$l]
  expect_equal(as.character(width), c("1null", "1null"))
})

test_that("margin_part() mechanics work as expected", {

  t <- theme_gray() +
    theme(plot.margin = margin_part(b = 11))

  test <- calc_element("plot.margin", t)
  expect_equal(as.numeric(test), c(5.5, 5.5, 11, 5.5))

  t <- theme_gray() +
    theme(margins = margin_part(b = 11))

  test <- calc_element("plot.margin", t)
  expect_equal(as.numeric(test), c(5.5, 5.5, 11, 5.5))
})

test_that("theme() warns about conflicting palette options", {
  expect_silent(
    theme(palette.colour.discrete = c("dodgerblue", "orange"))
  )
  local_options(ggplot2.discrete.colour = c("red", "purple"))
  expect_snapshot_warning(
    theme(palette.colour.discrete = c("dodgerblue", "orange"))
  )
})

test_that("geom elements are inherited correctly", {

  GeomFoo <- ggproto("GeomFoo", GeomPoint)
  GeomBar <- ggproto("GeomBar", GeomFoo)

  p <- ggplot(data.frame(x = 1), aes(x, x)) +
    stat_identity(geom = GeomBar) +
    theme(
      geom = element_geom(pointshape = 15),
      geom.point = element_geom(borderwidth = 2, ink = "blue"),
      geom.foo = element_geom(pointsize = 2),
      geom.bar = element_geom(ink = "red")
    )
  p <- layer_data(p)
  expect_equal(p$shape, 15)
  expect_equal(p$stroke, 2)
  expect_equal(p$size, 2)
  expect_equal(p$colour, "red")
})

test_that("theme elements are covered in `theme_sub_*()` functions", {
  # We use a snapshot test here to trigger when a new theme element is added
  # or removed.
  # A failure of this test should be taken as a prompt to see if the new
  # theme element should be included in one of the `theme_sub_*` functions.

  fmls <- paste0("axis.", fn_fmls_names(theme_sub_axis))
  fmls <- c(fmls, paste0("axis.",   fn_fmls_names(theme_sub_axis_x), ".x"))
  fmls <- c(fmls, paste0("axis.",   fn_fmls_names(theme_sub_axis_y), ".y"))
  fmls <- c(fmls, paste0("axis.",   fn_fmls_names(theme_sub_axis_top), ".x.top"))
  fmls <- c(fmls, paste0("axis.",   fn_fmls_names(theme_sub_axis_bottom), ".x.bottom"))
  fmls <- c(fmls, paste0("axis.",   fn_fmls_names(theme_sub_axis_left), ".y.left"))
  fmls <- c(fmls, paste0("axis.",   fn_fmls_names(theme_sub_axis_right), ".y.right"))
  fmls <- c(fmls, paste0("legend.", fn_fmls_names(theme_sub_legend)))
  fmls <- c(fmls, paste0("plot.",   fn_fmls_names(theme_sub_plot)))
  fmls <- c(fmls, paste0("panel.",  fn_fmls_names(theme_sub_panel)))
  fmls <- c(fmls, paste0("strip.",  fn_fmls_names(theme_sub_strip)))

  extra_elements <- setdiff(fn_fmls_names(theme), fmls)
  expect_snapshot(extra_elements)
})

# Visual tests ------------------------------------------------------------

test_that("element_polygon() can render a grob", {

  t <- theme_gray() + theme(polygon = element_polygon(fill = "orchid"))
  e <- calc_element("polygon", t)
  g <- element_grob(
    e,
    x  = c(0, 0.5, 1, 0.5, 0.15, 0.85, 0.85, 0.15),
    y  = c(0.5, 0, 0.5, 1, 0.15, 0.15, 0.85, 0.85),
    id = c(1, 1, 1, 1, 2, 2, 2, 2),
    colour = c("orange", "limegreen")
  )

  expect_s3_class(g, "pathgrob")
  expect_equal(g$gp$fill, "orchid")

  expect_doppelganger(
    "polygon elements",
    function() {grid.newpage(); grid.draw(g)}
  )
})

test_that("element_point() can render a grob", {

  t <- theme_gray() + theme(point = element_point(shape = 21, size = 5))
  e <- calc_element("point", t)
  g <- element_grob(
    e,
    x = seq(0.1, 0.9, length.out = 5),
    y = seq(0.9, 0.1, length.out = 5),
    fill = c("orange", "limegreen", "orchid", "turquoise", "grey")
  )

  expect_s3_class(g, "points")
  expect_equal(g$pch, 21)

  expect_doppelganger(
    "point elements",
    function() {grid.newpage(); grid.draw(g)}
  )
})

test_that("aspect ratio is honored", {
  df <- cbind(data_frame(x = 1:8, y = 1:8, f = gl(2,4)), expand.grid(f1 = 1:2, f2 = 1:2, rep = 1:2))
  p <- ggplot(df, aes(x, y)) +
    geom_point() +
    theme_test() +
    labs(x = NULL, y = NULL)

  p_a <- p + theme(aspect.ratio = 3)
  p_b <- p + theme(aspect.ratio = 1 / 3)

  expect_doppelganger("height is 3 times width",
    p_a
  )
  expect_doppelganger("width is 3 times height",
    p_b
  )

  expect_doppelganger("height is 3 times width, 2 wrap facets",
    p_a + facet_wrap(~f)
  )
  expect_doppelganger("height is 3 times width, 2 column facets",
    p_a + facet_grid(.~f)
  )
  expect_doppelganger("height is 3 times width, 2 row facets",
    p_a + facet_grid(f~.)
  )
  expect_doppelganger("height is 3 times width, 2x2 facets",
    p_a + facet_grid(f1~f2)
  )

})

test_that("themes don't change without acknowledgement", {
  df <- data_frame(x = 1:3, y = 1:3, z = c("a", "b", "a"), a = 1)
  plot <- ggplot(df, aes(x, y, colour = z)) +
    geom_point() +
    facet_wrap(~ a)

  expect_doppelganger("theme_bw", plot + theme_bw())
  expect_doppelganger("theme_classic", plot + theme_classic())
  expect_doppelganger("theme_dark", plot + theme_dark())
  expect_doppelganger("theme_minimal", plot + theme_minimal())
  expect_doppelganger("theme_gray", plot + theme_gray())
  expect_doppelganger("theme_light", plot + theme_light())
  expect_doppelganger("theme_void", plot + theme_void())
  expect_doppelganger("theme_linedraw", plot + theme_linedraw())
})

test_that("themes look decent at larger base sizes", {
  df <- data_frame(x = 1:3, y = 1:3, z = c("a", "b", "a"), a = 1)
  plot <- ggplot(df, aes(x, y, colour = z)) +
    geom_point() +
    facet_wrap(~ a)

  expect_doppelganger("theme_bw_large", plot + theme_bw(base_size = 33))
  expect_doppelganger("theme_classic_large", plot + theme_classic(base_size = 33))
  expect_doppelganger("theme_dark_large", plot + theme_dark(base_size = 33))
  expect_doppelganger("theme_minimal_large", plot + theme_minimal(base_size = 33))
  expect_doppelganger("theme_gray_large", plot + theme_gray(base_size = 33))
  expect_doppelganger("theme_light_large", plot + theme_light(base_size = 33))
  expect_doppelganger("theme_void_large", plot + theme_void(base_size = 33))
  expect_doppelganger("theme_linedraw_large", plot + theme_linedraw(base_size = 33))
})

test_that("setting 'spacing' and 'margins' affect the whole plot", {

  df <- data_frame(x = 1:3, y = 1:3, z = c("a", "b", "a"), a = 1)
  plot <- ggplot(df, aes(x, y, colour = z)) +
    geom_point() +
    facet_wrap(~ a) +
    theme_gray()

  expect_doppelganger("large spacing", plot + theme(spacing = unit(1, "cm")))
  expect_doppelganger("large margins", plot + theme(margins = margin(1, 1, 1, 1, "cm")))

})

test_that("axes can be styled independently", {
  plot <- ggplot() +
    geom_point(aes(1:10, 1:10)) +
    scale_x_continuous(sec.axis = dup_axis()) +
    scale_y_continuous(sec.axis = dup_axis()) +
    theme(
      axis.title.x.top = element_text(colour = 'red'),
      axis.title.x.bottom = element_text(colour = 'green'),
      axis.title.y.left = element_text(colour = 'blue'),
      axis.title.y.right = element_text(colour = 'yellow'),
      axis.text.x.top = element_text(colour = 'red'),
      axis.text.x.bottom = element_text(colour = 'green'),
      axis.text.y.left = element_text(colour = 'blue'),
      axis.text.y.right = element_text(colour = 'yellow'),
      axis.ticks.x.top = element_line(colour = 'red'),
      axis.ticks.x.bottom = element_line(colour = 'green'),
      axis.ticks.y.left = element_line(colour = 'blue'),
      axis.ticks.y.right = element_line(colour = 'yellow'),
      axis.line.x.top = element_line(colour = 'red'),
      axis.line.x.bottom = element_line(colour = 'green'),
      axis.line.y.left = element_line(colour = 'blue'),
      axis.line.y.right = element_line(colour = 'yellow')
    )
  expect_doppelganger("axes_styling", plot)
})

test_that("axes ticks can have independent lengths", {
  plot <- ggplot() +
    theme_test() +
    geom_point(aes(1:10, 1:10)) +
    scale_x_continuous(sec.axis = dup_axis()) +
    scale_y_continuous(sec.axis = dup_axis()) +
    theme(
      axis.ticks.length.x.top = unit(-0.5, "cm"),
      axis.ticks.length.x.bottom = unit(-0.25, "cm"),
      axis.ticks.length.y.left = unit(0.25, "cm"),
      axis.ticks.length.y.right = unit(0.5, "cm"),
      axis.text.x.bottom = element_text(margin = margin(t = 0.25, unit = "cm")),
      axis.text.x.top = element_text(margin = margin(b = 0.25, unit = "cm"))
    )
  expect_doppelganger("ticks_length", plot)
})

test_that("strips can be styled independently", {
  df <- data_frame(x = 1:2, y = 1:2)
  plot <- ggplot(df, aes(x, y)) +
    facet_grid(x ~ y) +
    theme(
      strip.background.x = element_rect(fill = "red"),
      strip.background.y = element_rect(fill = "green")
    )
  expect_doppelganger("strip_styling", plot)
})

test_that("rotated axis tick labels work", {
  df <- data_frame(
    y = c(1, 2, 3),
    label = c("short", "medium size", "very long label")
  )

  plot <- ggplot(df, aes(label, y)) + geom_point() +
    theme(axis.text.x = element_text(angle = 50, hjust = 1))
  expect_doppelganger("rotated x axis tick labels", plot)
})

test_that("plot titles and caption can be aligned to entire plot", {
  df <- data_frame(
    x = 1:3,
    y = 1:3,
    z = letters[1:3]
  )

  plot <- ggplot(df, aes(x, y, color = z)) +
    geom_point() + facet_wrap(~z) +
    labs(
      title = "Plot title aligned to entire plot",
      subtitle = "Subtitle aligned to entire plot",
      caption = "Caption aligned to panels"
    ) +
    theme(plot.title.position = "plot")
  expect_doppelganger("titles aligned to entire plot", plot)

  plot <- ggplot(df, aes(x, y, color = z)) +
    geom_point() + facet_wrap(~z) +
    labs(
      title = "Plot title aligned to panels",
      subtitle = "Subtitle aligned to panels",
      caption = "Caption aligned to entire plot"
    ) +
    theme(plot.caption.position = "plot")
  expect_doppelganger("caption aligned to entire plot", plot)

})

test_that("Legends can on all sides of the plot with custom justification", {

  plot <- ggplot(mtcars) +
    aes(
      disp, mpg,
      colour = hp,
      fill   = factor(gear),
      shape  = factor(cyl),
      size   = drat,
      alpha = wt
    ) +
    geom_point() +
    guides(
      shape  = guide_legend(position = "top"),
      colour = guide_colourbar(position = "bottom"),
      size   = guide_legend(position = "left"),
      alpha  = guide_legend(position = "right"),
      fill   = guide_legend(position = "inside", override.aes = list(shape = 21))
    ) +
    theme_test() +
    theme(
      legend.justification.top    = "left",
      legend.justification.bottom = c(1, 0),
      legend.justification.left   = c(0, 1),
      legend.justification.right  = "bottom",
      legend.justification.inside = c(0.75, 0.75),
      legend.location = "plot"
    )

  expect_doppelganger("legends at all sides with justification", plot)
})

test_that("Strips can render custom elements", {
  element_test <- S7::new_class("element_test", element_text)
  S7::method(element_grob, element_test) <-
    function(element, label = "", x = NULL, y = NULL, ...) {
      rectGrob(width = unit(1, "cm"), height = unit(1, "cm"))
    }

  df <- data_frame(x = 1:3, y = 1:3, a = letters[1:3])
  plot <- ggplot(df, aes(x, y)) +
    geom_point() +
    facet_wrap(~a) +
    theme(strip.text = element_test())
  expect_doppelganger("custom strip elements can render", plot)
})

test_that("theme ink and paper settings work", {

  p <- ggplot(mpg, aes(displ, hwy, colour = drv)) +
    geom_point() +
    facet_wrap(~"Strip title") +
    labs(
      title = "Main title",
      subtitle = "Subtitle",
      tag = "A",
      caption = "Caption"
    )

  expect_doppelganger(
    "Theme with inverted colours",
    p + theme_gray(ink = "white", paper = "black")
  )
})

test_that("legend margins are correct when using relative key sizes", {

  df <- data_frame(x = 1:3, y = 1:3, a = letters[1:3])
  p <- ggplot(df, aes(x, y, colour = x, shape = a)) +
    geom_point() +
    theme_test() +
    theme(
      legend.box.background = element_rect(colour = "blue", fill = NA),
      legend.background = element_rect(colour = "red", fill = NA)
    )

  vertical <- p + guides(
    colour = guide_colourbar(theme = theme(legend.key.height = unit(1, "null"))),
    shape  = guide_legend(theme = theme(legend.key.height = unit(1/3, "null")))
  ) + theme(
    legend.box.margin = margin(t = 5, b = 10, unit = "mm"),
    legend.margin = margin(t = 10, b = 5, unit = "mm")
  )

  expect_doppelganger("stretched vertical legends", vertical)

  horizontal <- p + guides(
    colour = guide_colourbar(theme = theme(legend.key.width = unit(1, "null"))),
    shape  = guide_legend(theme = theme(legend.key.width = unit(1/3, "null")))
  ) + theme(
    legend.position = "top",
    legend.box.margin = margin(l = 5, r = 10, unit = "mm"),
    legend.margin = margin(l = 10, r = 5, unit = "mm")
  )

  expect_doppelganger("stretched horizontal legends", horizontal)
})

test_that("legends are placed correctly when using stretchy spacing", {

  df <- data.frame(x = 1:3, y = 1:3, a = letters[1:3])

  p <- ggplot(df, aes(x, y, colour = a, shape = factor(x))) +
    geom_point() +
    theme(
      legend.box.background = element_rect(colour = "blue", fill = NA),
      legend.background = element_rect(colour = "red", fill = NA)
    )

  expect_doppelganger(
    "vertical legends placed apart",
    p + theme(legend.position = "right", legend.spacing.y = unit(1, "null"))
  )

  expect_doppelganger(
    "horizontal legends placed apart",
    p + theme(legend.position = "top", legend.spacing.x = unit(1, "null"))
  )
})
