test_that("a force profile gathers comparisons, ranks, bounds, change and coverage", { p <- btg_profile(force = "metropolitan", draws = 300) expect_s3_class(p, "btg_profile") expect_equal(p$area$level, "force") expect_equal(p$area$label, "Metropolitan Police") expect_equal(p$year, 2025L) cmp <- p$comparisons expect_identical(cmp$group, c("Black", "Asian", "Mixed", "Other")) black <- cmp[cmp$group == "Black", ] expect_equal(black$group_stops, 56116) expect_equal(black$ratio, (56116 / 1188137) / (48798 / 4725217)) expect_true(black$eligible) expect_true(black$rank_lower <= black$rank && black$rank <= black$rank_upper) expect_true(black$rank_best <= black$rank && black$rank <= black$rank_worst) expect_equal(black$n_eligible, 40L) expect_true(black$ratio_min < black$ratio && black$ratio < black$ratio_max) expect_equal(black$parity_direction, "above") expect_equal(nrow(p$stability), sum(btg_get_data("force_ethnicity_year", years = 2025)$data$eligible)) expect_equal(p$change$year_from, rep(2024L, 4)) expect_equal(p$coverage$status, "all_months_supplied") expect_equal(nrow(p$coverage$months), 12) expect_true(nrow(p$section60) >= 1 && all(p$section60$group %in% c("Asian", "Black", "Mixed", "Other", "White", "Unknown"))) expect_equal(nrow(p$national$year), 1) expect_true(length(p$statements) >= 8) expect_true(all(names(p$statements) %in% c("coverage", "Black", "Asian", "Mixed", "Other", "section60", "national"))) expect_identical(p$topics$topic, unique(names(p$statements))) expect_match(p$topics$heading[p$topics$topic == "Black"], "Black people") expect_match(p$statements[1], "all 12 months of 2025") expect_true(any(grepl("ranks .* of 40 forces", p$statements))) expect_true(any(grepl("no recorded ethnicity", p$statements))) expect_true(any(grepl("Compared with 2024", p$statements))) expect_true(any(grepl("Section 60", p$statements))) expect_true(any(grepl("England and Wales", p$statements))) expect_output(print(p), "Metropolitan Police, 2025") expect_output(print(p), "Reporting coverage") expect_equal(length(p$caveats), 5) }) test_that("profiles handle missing returns, earlier years and councils", { gm <- btg_profile(force = "greater-manchester", draws = 200) expect_equal(gm$coverage$status, "no_months_supplied") expect_match(gm$statements[1], "no supplied returns") expect_true(all(is.na(gm$comparisons$ratio))) expect_false(any(grepl("ranks", gm$statements))) early <- btg_profile(force = "dorset", year = 2019, draws = 200) expect_equal(early$year, 2019L) expect_match(early$statements[1], "unverified") expect_equal(unique(early$change$year_from), 2018L) first <- btg_profile(force = "dorset", year = 2018, draws = 200) expect_null(first$change) partial <- btg_profile(force = "metropolitan", year = 2026, draws = 200) expect_true(any(grepl("first 7 months of 2026", partial$statements))) lambeth <- btg_profile(council = "E09000022", draws = 200) expect_equal(lambeth$area$level, "council") expect_equal(lambeth$area$label, "Lambeth") expect_null(lambeth$coverage) expect_null(lambeth$stability) expect_null(lambeth$section60) expect_false("rank" %in% names(lambeth$comparisons)) expect_match(lambeth$statements[1], "stop coordinates") expect_true(all(lambeth$comparisons$ratio_min <= lambeth$comparisons$ratio * (1 + 1e-9), na.rm = TRUE)) expect_error(btg_profile(), "one force or one council") expect_error(btg_profile(force = "metropolitan", council = "E09000022"), "one force or one council") expect_error(btg_profile(force = "nowhere"), "identifiers") expect_error(btg_profile(force = "metropolitan", year = 2017), "calendar year") }) test_that("the briefing renders to a self-contained HTML file", { skip_if_not_installed("rmarkdown") skip_if_not(rmarkdown::pandoc_available("2.0")) path <- tempfile(fileext = ".html") on.exit(unlink(path)) expect_error(btg_report(force = "metropolitan", file = path, overwrite = "yes"), "overwrite") expect_error(btg_report(file = path), "one force or one council") out <- btg_report(force = "metropolitan", file = path, draws = 200) expect_identical(out, path) expect_true(file.exists(path)) html <- paste(readLines(path, warn = FALSE), collapse = "\n") expect_match(html, "Metropolitan Police, 2025", fixed = TRUE) expect_match(html, "Headline findings", fixed = TRUE) expect_match(html, "At a glance", fixed = TRUE) expect_match(html, 'class="btg-table"', fixed = TRUE) expect_match(html, 'class="btg-card"', fixed = TRUE) expect_match(html, "Table 1.", fixed = TRUE) expect_match(html, "Figure 1.", fixed = TRUE) # Section 60 shares are computed row by row (2,200 of 3,024 searches were of Black people) expect_match(html, "72.8%", fixed = TRUE) expect_match(html, "Reproduce it", fixed = TRUE) expect_match(html, "data:image", fixed = TRUE) expect_error(btg_report(force = "metropolitan", file = path), "File exists") council <- btg_report(council = "E09000022", file = path, overwrite = TRUE, draws = 200) expect_match(paste(readLines(council, warn = FALSE), collapse = "\n"), "Lambeth, 2025", fixed = TRUE) expect_error(btg_report(force = "kent", file = path, format = "txt", overwrite = TRUE), "format must be") }) test_that("the briefing also renders as a Word document", { skip_if_not_installed("rmarkdown") skip_if_not(rmarkdown::pandoc_available("2.0")) path <- tempfile(fileext = ".docx") on.exit(unlink(path)) out <- btg_report(force = "kent", year = 2024, file = path, draws = 200) expect_true(file.exists(path)) expect_gt(file.info(path)$size, 50000) zipped <- utils::unzip(path, list = TRUE)$Name expect_true("word/document.xml" %in% zipped) expect_true(any(grepl("^word/media/", zipped))) styles <- rawToChar(utils::unzip(path, "word/styles.xml", exdir = tempdir()) |> readBin("raw", 1e6)) expect_match(styles, "0B2545", fixed = TRUE) }) test_that("the briefing renders as a PDF where LaTeX is available", { skip_if_not_installed("rmarkdown") skip_if_not(rmarkdown::pandoc_available("2.0")) skip_if_not(nzchar(Sys.which("pdflatex")), "no LaTeX") path <- tempfile(fileext = ".pdf") on.exit(unlink(path)) out <- btg_report(force = "kent", year = 2024, file = path, draws = 200) expect_true(file.exists(path)) expect_identical(rawToChar(readBin(path, "raw", 5)), "%PDF-") expect_gt(file.info(path)$size, 50000) }) test_that("tables take the form of the document being knitted", { skip_if_not_installed("knitr") x <- head(btg_get_data("national_year")$data[c("year", "stops", "arrest_rate")], 3) plain <- btg_table(x, caption = "National totals") expect_s3_class(plain, "knitr_kable") expect_match(paste(plain, collapse = "\n"), "National totals", fixed = TRUE) expect_error(btg_table(1:3), "data frame") })