Files
civilyticsR/tests/testthat/test_theme.R
T
jaredandClaude Opus 4.6 328d9d2fd6
R-CMD-check / R CMD check (push) Successful in 3m37s
feat: add position argument to civilytics_logo() for corner placement
Supports "bottom-right" (default), "bottom-left", "top-right", and
"top-left". The position controls both horizontal alignment of the logo
grob and whether it is placed above or below the plot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 15:44:18 -06:00

334 lines
11 KiB
R

library(ggplot2)
# --- civilytics_colors -------------------------------------------------------
test_that("civilytics_colors is a named character vector of hex codes", {
expect_type(civilytics_colors, "character")
expect_named(civilytics_colors)
expect_true(all(grepl("^#[0-9A-Fa-f]{6}$", civilytics_colors)))
})
test_that("civilytics_colors contains expected brand keys", {
expected <- c("paper", "ink", "navy_600", "ember_600", "teal_600",
"plum_600", "moss_600", "paper_2", "rule")
expect_true(all(expected %in% names(civilytics_colors)))
})
test_that("civilytics_colors values match brand specification", {
expect_equal(unname(civilytics_colors["paper"]), "#FAF7F2")
expect_equal(unname(civilytics_colors["ink"]), "#0E1A2B")
expect_equal(unname(civilytics_colors["ember_600"]), "#C25311")
expect_equal(unname(civilytics_colors["navy_700"]), "#1A2E4A")
expect_equal(unname(civilytics_colors["ember_400"]), "#EA8A49")
})
# --- civilytics_palettes / civilytics_palette --------------------------------
test_that("civilytics_palettes contains all expected palettes", {
expected <- c("qual", "qual_warm", "qual_cool",
"seq_ember", "seq_navy", "seq_violet", "seq_paper_ink",
"div_navy_ember", "div_violet_ember")
expect_true(all(expected %in% names(civilytics_palettes)))
})
test_that("civilytics_palette returns character vector of hex codes", {
result <- civilytics_palette()
expect_type(result, "character")
expect_true(all(grepl("^#[0-9A-Fa-f]{6}$", result)))
})
test_that("civilytics_palette respects n argument", {
expect_length(civilytics_palette("qual", n = 3), 3)
expect_length(civilytics_palette("seq_navy", n = 5), 5)
})
test_that("civilytics_palette interpolates sequential palettes", {
result <- civilytics_palette("seq_ember", n = 20)
expect_length(result, 20)
expect_true(all(grepl("^#[0-9A-Fa-f]{6}$", result)))
})
test_that("civilytics_palette warns when recycling qualitative", {
expect_warning(civilytics_palette("qual", n = 10), "recycling")
})
test_that("civilytics_palette reverse works", {
fwd <- civilytics_palette("seq_navy")
rev <- civilytics_palette("seq_navy", reverse = TRUE)
expect_equal(fwd, rev(rev))
})
test_that("civilytics_palette errors on unknown palette name", {
expect_error(civilytics_palette("nope"), "'nope' is not a valid palette")
})
# --- civilytics_pal (closure) ------------------------------------------------
test_that("civilytics_pal returns a function", {
f <- civilytics_pal()
expect_type(f, "closure")
result <- f(4)
expect_length(result, 4)
expect_true(all(grepl("^#[0-9A-Fa-f]{6}$", result)))
})
# --- scale_color/fill_civilytics ---------------------------------------------
test_that("scale_color_civilytics returns a Scale object (discrete)", {
sc <- scale_color_civilytics()
expect_s3_class(sc, "Scale")
})
test_that("scale_color_civilytics returns a Scale object (continuous)", {
sc <- scale_color_civilytics("seq_navy", discrete = FALSE)
expect_s3_class(sc, "Scale")
})
test_that("scale_fill_civilytics returns a Scale object (discrete)", {
sc <- scale_fill_civilytics()
expect_s3_class(sc, "Scale")
})
test_that("scale_fill_civilytics returns a Scale object (continuous)", {
sc <- scale_fill_civilytics("seq_ember", discrete = FALSE)
expect_s3_class(sc, "Scale")
})
test_that("scale_color_civilytics reverse parameter works", {
sc <- scale_color_civilytics("qual", reverse = TRUE)
expect_s3_class(sc, "Scale")
})
test_that("scale_color_civilytics works in a ggplot", {
p <- ggplot(mpg, aes(displ, hwy, colour = class)) +
geom_point() +
scale_color_civilytics()
expect_no_error(ggplot_build(p))
})
test_that("scale_fill_civilytics works in a ggplot", {
p <- ggplot(mpg, aes(class, fill = class)) +
geom_bar() +
scale_fill_civilytics()
expect_no_error(ggplot_build(p))
})
# --- theme_civilytics --------------------------------------------------------
test_that("theme_civilytics returns a complete ggplot2 theme", {
th <- theme_civilytics()
expect_s3_class(th, "theme")
expect_true(attr(th, "complete"))
})
test_that("theme_civilytics applies to a ggplot without error", {
p <- ggplot(mpg, aes(displ, hwy)) +
geom_point() +
theme_civilytics()
expect_no_error(ggplot_build(p))
})
test_that("theme_civilytics uses brand ink color for text", {
th <- theme_civilytics()
expect_equal(th$text$colour, unname(civilytics_colors["ink"]))
})
test_that("theme_civilytics uses brand paper color for plot background", {
th <- theme_civilytics()
expect_equal(th$plot.background$fill, unname(civilytics_colors["paper"]))
})
test_that("theme_civilytics uses paper_2 for strip background by default", {
th <- theme_civilytics()
expect_equal(th$strip.background$fill, unname(civilytics_colors["paper_2"]))
})
test_that("theme_civilytics strip_color parameter is respected", {
th <- theme_civilytics(strip_color = "#FF0000")
expect_equal(th$strip.background$fill, "#FF0000")
})
test_that("theme_civilytics font_size parameter scales text elements", {
th_big <- theme_civilytics(font_size = 20)
th_sml <- theme_civilytics(font_size = 10)
expect_gt(th_big$text$size, th_sml$text$size)
})
test_that("theme_civilytics grid parameter controls gridlines", {
th_y <- theme_civilytics(grid = "y")
th_x <- theme_civilytics(grid = "x")
th_both <- theme_civilytics(grid = "both")
th_none <- theme_civilytics(grid = "none")
# grid="y" shows y gridlines, blanks x
expect_s3_class(th_y$panel.grid.major.y, "element_line")
expect_s3_class(th_y$panel.grid.major.x, "element_blank")
# grid="x" shows x gridlines, blanks y
expect_s3_class(th_x$panel.grid.major.x, "element_line")
expect_s3_class(th_x$panel.grid.major.y, "element_blank")
# grid="both" shows both
expect_s3_class(th_both$panel.grid.major.x, "element_line")
expect_s3_class(th_both$panel.grid.major.y, "element_line")
# grid="none" blanks both
expect_s3_class(th_none$panel.grid.major.x, "element_blank")
expect_s3_class(th_none$panel.grid.major.y, "element_blank")
})
test_that("theme_civilytics paper_bg=FALSE gives transparent background", {
th <- theme_civilytics(paper_bg = FALSE)
expect_true(is.na(th$plot.background$fill))
expect_true(is.na(th$panel.background$fill))
})
test_that("theme_civilytics has plot-aligned title and caption", {
th <- theme_civilytics()
expect_equal(th$plot.title.position, "plot")
expect_equal(th$plot.caption.position, "plot")
})
# --- theme_civilytics_dark ---------------------------------------------------
test_that("theme_civilytics_dark returns a complete ggplot2 theme", {
th <- theme_civilytics_dark()
expect_s3_class(th, "theme")
expect_true(attr(th, "complete"))
})
test_that("theme_civilytics_dark applies to a ggplot without error", {
p <- ggplot(mpg, aes(displ, hwy)) +
geom_point() +
theme_civilytics_dark()
expect_no_error(ggplot_build(p))
})
test_that("theme_civilytics_dark uses paper color as ink (light text on dark bg)", {
th <- theme_civilytics_dark()
expect_equal(th$text$colour, unname(civilytics_colors["paper"]))
})
test_that("theme_civilytics_dark uses navy_700 as background", {
th <- theme_civilytics_dark()
expect_equal(th$plot.background$fill, unname(civilytics_colors["navy_700"]))
})
test_that("theme_civilytics_dark uses navy_600 for strip background", {
th <- theme_civilytics_dark()
expect_equal(th$strip.background$fill, unname(civilytics_colors["navy_600"]))
})
test_that("theme_civilytics_dark inherits grid parameter", {
th <- theme_civilytics_dark(grid = "both")
expect_s3_class(th$panel.grid.major.x, "element_line")
expect_s3_class(th$panel.grid.major.y, "element_line")
})
# --- theme_civilytics_slide --------------------------------------------------
test_that("theme_civilytics_slide returns a complete ggplot2 theme", {
th <- theme_civilytics_slide()
expect_s3_class(th, "theme")
expect_true(attr(th, "complete"))
})
test_that("theme_civilytics_slide has transparent background by default", {
th <- theme_civilytics_slide()
expect_true(is.na(th$plot.background$fill))
})
test_that("theme_civilytics_slide uses larger base font", {
th_slide <- theme_civilytics_slide()
th_base <- theme_civilytics()
expect_gt(th_slide$text$size, th_base$text$size)
})
test_that("theme_civilytics_slide applies to a ggplot without error", {
p <- ggplot(mpg, aes(displ, hwy, colour = class)) +
geom_point() +
scale_color_civilytics() +
theme_civilytics_slide()
expect_no_error(ggplot_build(p))
})
# --- make_logo_grob ----------------------------------------------------------
test_that("make_logo_grob returns a gg object for all type/variant combos", {
for (type in c("wordmark", "mark")) {
for (variant in c("light", "dark")) {
logo <- make_logo_grob(type = type, variant = variant)
expect_s3_class(logo, "gg")
}
}
})
test_that("make_logo_grob defaults to wordmark light", {
expect_no_error(make_logo_grob())
})
test_that("make_logo_grob errors on invalid type", {
expect_error(make_logo_grob(type = "banner"))
})
test_that("make_logo_grob errors on invalid variant", {
expect_error(make_logo_grob(variant = "purple"))
})
# --- civilytics_logo (pipe-friendly) -----------------------------------------
test_that("civilytics_logo returns a grob", {
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics()
result <- suppressWarnings(civilytics_logo(p))
expect_s3_class(result, "grob")
})
test_that("civilytics_logo works with pipe operator", {
# |> has higher precedence than +, so parens are required
result <- suppressWarnings(
(ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics()) |>
civilytics_logo()
)
expect_s3_class(result, "grob")
})
test_that("civilytics_logo accepts type and variant params", {
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics_dark()
result <- suppressWarnings(civilytics_logo(p, type = "mark", variant = "dark"))
expect_s3_class(result, "grob")
})
# --- position argument -------------------------------------------------------
test_that("civilytics_logo accepts all four position values", {
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics()
for (pos in c("bottom-right", "bottom-left", "top-right", "top-left")) {
result <- suppressWarnings(civilytics_logo(p, position = pos))
expect_s3_class(result, "grob")
}
})
test_that("civilytics_logo errors on invalid position", {
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics()
expect_error(civilytics_logo(p, position = "center"))
})
test_that("make_logo_grob accepts position argument", {
for (pos in c("bottom-right", "bottom-left", "top-right", "top-left")) {
logo <- make_logo_grob(position = pos)
expect_s3_class(logo, "gg")
}
})
test_that("top positions place logo before plot in arrangeGrob", {
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + theme_civilytics()
result_top <- suppressWarnings(civilytics_logo(p, position = "top-right"))
result_bot <- suppressWarnings(civilytics_logo(p, position = "bottom-right"))
# Top: logo is first grob; bottom: plot is first grob.
# The arrangeGrob heights differ in order
expect_s3_class(result_top, "grob")
expect_s3_class(result_bot, "grob")
})