|
| 1 | +# Tests for aesthetic correctness: |
| 2 | +# - pip colours are never NA when dots are visible |
| 3 | +# - pip coordinates fall within tile boundaries |
| 4 | +# - pip row count matches the data (no silent drops) |
| 5 | + |
| 6 | +# --------------------------------------------------------------------------- |
| 7 | +# Helper: extract point_df from the first layer's DiceGrob. |
| 8 | +# Uses the same tree-traversal approach as test-rendering.R. |
| 9 | +# --------------------------------------------------------------------------- |
| 10 | +find_dice_grob_aes <- function(x) { |
| 11 | + if (inherits(x, "DiceGrob")) return(x) |
| 12 | + kids <- if (inherits(x, "gTree")) x$children else if (is.list(x)) x else NULL |
| 13 | + if (!is.null(kids)) { |
| 14 | + for (k in kids) { |
| 15 | + found <- find_dice_grob_aes(k) |
| 16 | + if (!is.null(found)) return(found) |
| 17 | + } |
| 18 | + } |
| 19 | + NULL |
| 20 | +} |
| 21 | + |
| 22 | +get_point_df <- function(plot) { |
| 23 | + raw <- ggplot2::layer_grob(plot, i = 1L) |
| 24 | + g <- find_dice_grob_aes(raw) |
| 25 | + if (is.null(g)) stop("DiceGrob not found in layer_grob() output") |
| 26 | + g$point_df |
| 27 | +} |
| 28 | + |
| 29 | +# --------------------------------------------------------------------------- |
| 30 | +# Colour correctness |
| 31 | +# --------------------------------------------------------------------------- |
| 32 | + |
| 33 | +test_that("pip colour is never NA (fill unmapped)", { |
| 34 | + p <- make_simple_plot(fill_mapped = FALSE) |
| 35 | + pdf <- get_point_df(p) |
| 36 | + expect_false( |
| 37 | + any(is.na(pdf$colour)), |
| 38 | + info = "All pip colours must be non-NA when fill is not mapped" |
| 39 | + ) |
| 40 | +}) |
| 41 | + |
| 42 | +test_that("pip colour is never NA (fill mapped to continuous)", { |
| 43 | + dat <- make_simple_data() |
| 44 | + p <- ggplot2::ggplot(dat, ggplot2::aes(x = x, y = y, dots = dots, |
| 45 | + fill = fill_val)) + |
| 46 | + geom_dice(ndots = 3L, x_length = 2L, y_length = 1L) + |
| 47 | + ggplot2::scale_fill_gradient2(low = "blue", high = "red", mid = "white", |
| 48 | + midpoint = 0) |
| 49 | + pdf <- get_point_df(p) |
| 50 | + expect_false( |
| 51 | + any(is.na(pdf$colour)), |
| 52 | + info = "All pip colours must be non-NA when fill is a continuous aesthetic" |
| 53 | + ) |
| 54 | +}) |
| 55 | + |
| 56 | +test_that("pip colour is never NA (fill mapped to discrete)", { |
| 57 | + dat <- make_simple_data() |
| 58 | + dat$group <- factor(ifelse(dat$fill_val > 0, "Up", "Down")) |
| 59 | + p <- ggplot2::ggplot(dat, ggplot2::aes(x = x, y = y, dots = dots, |
| 60 | + fill = group)) + |
| 61 | + geom_dice(ndots = 3L, x_length = 2L, y_length = 1L) |
| 62 | + pdf <- get_point_df(p) |
| 63 | + expect_false( |
| 64 | + any(is.na(pdf$colour)), |
| 65 | + info = "All pip colours must be non-NA when fill is a discrete aesthetic" |
| 66 | + ) |
| 67 | +}) |
| 68 | + |
| 69 | +# --------------------------------------------------------------------------- |
| 70 | +# Coordinate bounds: pips must lie within their tile boundaries |
| 71 | +# --------------------------------------------------------------------------- |
| 72 | + |
| 73 | +test_that("pip x-coordinates fall within tile boundaries", { |
| 74 | + dat <- make_simple_data() |
| 75 | + tile_w <- 0.5 # default width |
| 76 | + p <- ggplot2::ggplot(dat, ggplot2::aes(x = x, y = y, dots = dots)) + |
| 77 | + geom_dice(ndots = 3L, x_length = 2L, y_length = 1L) |
| 78 | + pdf <- get_point_df(p) |
| 79 | + |
| 80 | + # x_coord holds the tile centre; pips must stay within ± tile_w/2 |
| 81 | + tile_centers <- pdf$x_coord |
| 82 | + expect_true( |
| 83 | + all(pdf$x >= tile_centers - tile_w / 2 - .Machine$double.eps & |
| 84 | + pdf$x <= tile_centers + tile_w / 2 + .Machine$double.eps), |
| 85 | + info = "Pip x-coordinates must not exceed tile boundaries" |
| 86 | + ) |
| 87 | +}) |
| 88 | + |
| 89 | +test_that("pip y-coordinates fall within tile boundaries", { |
| 90 | + dat <- make_simple_data() |
| 91 | + tile_h <- 0.5 # default height |
| 92 | + p <- ggplot2::ggplot(dat, ggplot2::aes(x = x, y = y, dots = dots)) + |
| 93 | + geom_dice(ndots = 3L, x_length = 2L, y_length = 1L) |
| 94 | + pdf <- get_point_df(p) |
| 95 | + |
| 96 | + tile_centers <- pdf$y_coord |
| 97 | + expect_true( |
| 98 | + all(pdf$y >= tile_centers - tile_h / 2 - .Machine$double.eps & |
| 99 | + pdf$y <= tile_centers + tile_h / 2 + .Machine$double.eps), |
| 100 | + info = "Pip y-coordinates must not exceed tile boundaries" |
| 101 | + ) |
| 102 | +}) |
| 103 | + |
| 104 | +# --------------------------------------------------------------------------- |
| 105 | +# Pip count invariant: no silent row drops |
| 106 | +# --------------------------------------------------------------------------- |
| 107 | + |
| 108 | +test_that("pip count matches the number of data rows (no silent drops)", { |
| 109 | + dat <- make_simple_data() |
| 110 | + # dat has 5 rows => 5 dots expected (2 tiles: 3+2 dots) |
| 111 | + p <- ggplot2::ggplot(dat, ggplot2::aes(x = x, y = y, dots = dots)) + |
| 112 | + geom_dice(ndots = 3L, x_length = 2L, y_length = 1L) |
| 113 | + pdf <- get_point_df(p) |
| 114 | + expect_equal( |
| 115 | + nrow(pdf), nrow(dat), |
| 116 | + info = "point_df must have exactly one row per data observation" |
| 117 | + ) |
| 118 | +}) |
| 119 | + |
| 120 | +test_that("all ndots values produce the correct pip count for a full single tile", { |
| 121 | + for (n in 1:6) { |
| 122 | + dat <- make_single_tile_data(n) |
| 123 | + p <- ggplot2::ggplot(dat, ggplot2::aes(x = x, y = y, dots = dots)) + |
| 124 | + geom_dice(ndots = n, x_length = 1L, y_length = 1L) |
| 125 | + pdf <- get_point_df(p) |
| 126 | + expect_equal( |
| 127 | + nrow(pdf), n, |
| 128 | + info = paste0("Expected ", n, " pip rows for ndots = ", n) |
| 129 | + ) |
| 130 | + } |
| 131 | +}) |
| 132 | + |
| 133 | +# --------------------------------------------------------------------------- |
| 134 | +# Tile count invariant: one tile grob per unique (x, y) combination |
| 135 | +# --------------------------------------------------------------------------- |
| 136 | + |
| 137 | +test_that("tile_df has one row per unique (x, y) tile", { |
| 138 | + dat <- make_simple_data() |
| 139 | + # 2 unique (x, y) combinations: (1,1) and (2,1) |
| 140 | + p <- ggplot2::ggplot(dat, ggplot2::aes(x = x, y = y, dots = dots)) + |
| 141 | + geom_dice(ndots = 3L, x_length = 2L, y_length = 1L) |
| 142 | + raw <- ggplot2::layer_grob(p, i = 1L) |
| 143 | + g <- find_dice_grob_aes(raw) |
| 144 | + n_tiles <- nrow(unique(dat[, c("x", "y")])) |
| 145 | + expect_equal( |
| 146 | + nrow(g$tile_df), n_tiles, |
| 147 | + info = "tile_df must have exactly one row per unique tile" |
| 148 | + ) |
| 149 | +}) |
0 commit comments