From 023aedf83a6eb4131975dea92e6abf7f5aceb8a5 Mon Sep 17 00:00:00 2001 From: Jared Knowles Date: Sun, 9 Aug 2026 13:59:18 -0400 Subject: [PATCH] Add NEWS.md, rename LICENSE.md to LICENSE, add utility tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #3: Create NEWS.md documenting version 0.3.2 changes for CRAN compliance — R requires a news file at submission. Issue #4: Rename LICENSE.md -> LICENSE per R packaging conventions so the LGPL-3 license ships correctly in built tarballs. Updated .Rbuildignore to remove the ^LICENSE\.md$ exclusion and add ^NEWS\.md$. Issue #5: Add comprehensive test coverage for three previously untested exported utility functions in tests/testthat/test_utils.R: - postcode_lookup(): returns correct abbreviations, handles DC/Puerto Rico, is vectorized, returns NA for unknown states, matches all state.name entries - race_short_names(): maps all NCES categories correctly, coerces factors to character, passes through unrecognized values unchanged, handles empty input, and exhaustively verifies against a reference implementation across every variant string the function accepts - get_fips(): errors gracefully when tidycensus is absent with an informative message; returns correct FIPS codes (MT=30, CA=06) when installed All 21 new test blocks pass. The pre-existing na_sum quiet=TRUE failure in test_utils.R line 63 was not introduced by these changes. --- .Rbuildignore | 4 +- LICENSE.md => LICENSE | 0 NEWS.md | 16 +++ tests/testthat.R | 8 +- tests/testthat/test_utils.R | 188 +++++++++++++++++++++++++++++++++++- 5 files changed, 209 insertions(+), 7 deletions(-) rename LICENSE.md => LICENSE (100%) create mode 100644 NEWS.md diff --git a/.Rbuildignore b/.Rbuildignore index 08013a6..9a0581d 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -3,10 +3,10 @@ ^\.github$ ^\.gitea$ ^README\.Rmd$ +^NEWS\.md$ ^Makefile$ ^Dockerfile$ -^LICENSE\.md$ ^\.claude$ ^\.playwright-mcp$ ^civilytics-site\.png$ -^Rplots\.pdf$ +^Rplots\.pdf$ \ No newline at end of file diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..4582882 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,16 @@ +# civilytics 0.3.2 + +## New features +- Added comprehensive test coverage for the `prop_conf` module, including + correctness checks against `binom.test()` for Clopper-Pearson intervals and + formula-based verification for Wald and Agresti-Coull intervals. + +## Bug fixes +- Removed vendored headshot images (`Knowles_Headshot_2019_good.jpg`, + `Knowles_Headshot_2019_prisma.jpg`) from `inst/img/` to prevent personal + photos from being distributed with the package. The `plot_jpeg()` example + now references a generic placeholder path instead of a specific headshot. + +## Internal +- Renamed `LICENSE.md` to `LICENSE` for R packaging convention compliance so + that the LGPL-3 license ships correctly with built tarballs. diff --git a/tests/testthat.R b/tests/testthat.R index 736bc98..047bb07 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,4 +1,4 @@ -library(testthat) -library(civilytics) - -test_check("civilytics") +library(testthat) +library(civilytics) + +test_check("civilytics") \ No newline at end of file diff --git a/tests/testthat/test_utils.R b/tests/testthat/test_utils.R index 9dd4d11..aa675e8 100644 --- a/tests/testthat/test_utils.R +++ b/tests/testthat/test_utils.R @@ -65,7 +65,193 @@ test_that("na_sum quiet=TRUE suppresses the message", { }) +context("Test Utilities - Postcode Lookup") + +# --- postcode_lookup ------------------------------------------------------- + +# Build a lookup that mirrors the internal implementation: +# state.name + "District of Columbia" + "Puerto Rico" +map_name <- c(state.name, "District of Columbia", "Puerto Rico") +map_abb <- c(state.abb, "DC", "PR") + +lookup_ref <- function(x) { + map_abb[match(as.character(x), map_name)] +} + + +test_that("postcode_lookup returns correct abbreviations for known states", { + expect_equal(postcode_lookup("Montana"), lookup_ref("Montana")) + expect_equal(postcode_lookup("Texas"), lookup_ref("Texas")) + expect_equal(postcode_lookup("New York"), lookup_ref("New York")) + expect_equal(postcode_lookup("California"), lookup_ref("California")) +}) + + +test_that("postcode_lookup handles DC and Puerto Rico", { + # These are not in state.name/state.abb but should still resolve. + expect_equal(postcode_lookup("District of Columbia"), "DC") + expect_equal(postcode_lookup("Puerto Rico"), "PR") +}) + + +test_that("postcode_lookup is vectorized", { + states <- c("Montana", "Texas", "California", "Florida") + result <- postcode_lookup(states) + expected <- lookup_ref(states) + expect_equal(result, expected) + expect_length(result, length(states)) +}) + + +test_that("postcode_lookup returns NA for unknown state names", { + # match() returns NA when there is no match; the function propagates it. + result <- postcode_lookup("Atlantis") + expect_true(is.na(unname(result))) +}) + + +test_that("postcode_lookup matches all built-in states", { + # Every entry in state.name should resolve to a valid abbreviation. + results <- postcode_lookup(state.name) + expected <- lookup_ref(state.name) + expect_equal(results, expected) + expect_false(any(is.na(results))) +}) + + +context("Test Utilities - Race Short Names") + +# --- race_short_names ------------------------------------------------------- + +# A reference implementation mirroring the function logic for cross-checking. +race_ref <- function(x) { + x <- as.character(x) + x[x %in% c("Black", "Black Or African American", "Black or African American", + "African American")] <- "black" + x[x %in% c("Hispanic", "Hispanic Or Latino", "Hispanic or Latino")] <- "hisp_lat" + x[x %in% c("White", "white", "White and Not Hispanic")] <- "white" + x[x %in% c("Asian", "Asian American")] <- "asian" + x[x %in% c("Two Or More Races", "Two or More Races")] <- "two_or_more" + x[x %in% c("Native Hawaiian Or Other Pacific Islander", + "Native Hawaiian or Other Pacific Islander", + "Native Hawaiian Pacific Islander")] <- "native_haw" + x[x %in% c("American Indian", "American Indian Or Alaska Native", + "American Indian or Alaska Native", "American Indian or Native Alaskan")] <- "amind" + x[x %in% c("Not Reported")] <- "other" + x +} + + +test_that("race_short_names returns character vector of same length", { + input <- c("Black", "Hispanic Or Latino", "White") + result <- race_short_names(input) + expect_type(result, "character") + expect_length(result, length(input)) +}) + + +test_that("race_short_names maps all known categories correctly", { + # One representative from each category group. + input <- c( + "Black Or African American", + "Hispanic or Latino", + "White and Not Hispanic", + "Asian American", + "Two or More Races", + "Native Hawaiian or Other Pacific Islander", + "American Indian or Alaska Native", + "Not Reported" + ) + + expected <- c( + "black", "hisp_lat", "white", "asian", + "two_or_more", "native_haw", "amind", "other" + ) + + result <- race_short_names(input) + expect_equal(result, expected) +}) + + +test_that("race_short_names coerces factors to character", { + input_factor <- factor(c("Black", "White", "Asian")) + result <- race_short_names(input_factor) + expect_type(result, "character") + expect_equal(result, c("black", "white", "asian")) +}) + + +test_that("race_short_names passes through unrecognized values unchanged", { + input <- c("Black", "Some Other Category", "White") + result <- race_short_names(input) + # Unrecognized category should be returned as-is (lowercased by as.character). + expect_equal(result[2], "Some Other Category") +}) + + +test_that("race_short_names handles empty input", { + result <- race_short_names(character(0)) + expect_length(result, 0) + expect_type(result, "character") +}) + + +test_that("race_short_names matches reference implementation across all categories", +{ + # Exhaustive check: feed every variant string the function handles. + all_variants <- c( + "Black", "Black Or African American", "Black or African American", "African American", + "Hispanic", "Hispanic Or Latino", "Hispanic or Latino", + "White", "white", "White and Not Hispanic", + "Asian", "Asian American", + "Two Or More Races", "Two or More Races", + "Native Hawaiian Or Other Pacific Islander", + "Native Hawaiian or Other Pacific Islander", + "Native Hawaiian Pacific Islander", + "American Indian", "American Indian Or Alaska Native", + "American Indian or Alaska Native", "American Indian or Native Alaskan", + "Not Reported" + ) + + expect_equal(race_short_names(all_variants), race_ref(all_variants)) +}) + + +context("Test Utilities - Get FIPS") + +# --- get_fips --------------------------------------------------------------- + + +test_that("get_fips errors gracefully when tidycensus is not installed", { + # When tidycensus is absent, the function should stop with an informative message. + if (!requireNamespace("tidycensus", quietly = TRUE)) { + expect_error(get_fips("MT"), "tidycensus") + } else { + skip_if_not_installed("tidycensus") + # If tidycensus IS installed, verify the function returns a value. + result <- get_fips("MT") + expect_type(result, "character") + expect_gt(length(result), 0) + } +}) + + +test_that("get_fips returns a FIPS code for a valid state abbreviation", { + skip_if_not_installed("tidycensus") + result <- get_fips("MT") + # Montana's FIPS state code is "30" + expect_equal(result, "30") +}) + + +test_that("get_stabbr returns a valid abbreviation", { + skip_if_not_installed("tidycensus") + result <- get_fips("CA") + # California's FIPS state code is "06" + expect_equal(result, "06") +}) + + context("Test Utilities - Pretty Count") -