# Tests for chunkVector -------------------------------------------------------- # ------------------------------------------------------------------ # Test: chunkVector returns a single chunk when n_chunks <= 1 # Input: # - x = 1:10 # - n_chunks = 1 # Behaviour: # - returns list(x) (one chunk containing all elements) # Expectations: # - output is a list of length 1 # - the only element equals x exactly # Why: # - ensures the "no chunking" path is stable and predictable. # ------------------------------------------------------------------ test_that("chunkVector returns single chunk when n_chunks <= 1", { x <- 1:10 out <- chunkVector(x, n_chunks = 1) expect_type(out, "list") expect_length(out, 1) expect_identical(out[[1]], x) }) # ------------------------------------------------------------------ # Test: chunkVector splits into multiple chunks and preserves all elements # Input: # - x = 1:10 # - n_chunks = 2, 3 # Behaviour: # - splits x into n_chunks chunks # Expectations: # - output is a list with requested length # - unlisted output contains exactly the original elements # Why: # - verifies chunking doesn't drop or duplicate elements. # ------------------------------------------------------------------ test_that("chunkVector splits into multiple chunks and preserves elements", { x <- 1:10 out2 <- chunkVector(x, n_chunks = 2) expect_type(out2, "list") expect_length(out2, 2) expect_identical(sort(unlist(out2)), x) out3 <- chunkVector(x, n_chunks = 3) expect_type(out3, "list") expect_length(out3, 3) expect_identical(sort(unlist(out3)), x) }) # Tests for convertVector2Matrix ----------------------------------------------- # ------------------------------------------------------------------ # Test: convertVector2Matrix converts a vector to a 1-row matrix # Input: # - vector = c(1,2,3,4) # Behaviour: # - wraps into a matrix and transposes to 1 x length(vector) # Expectations: # - result is a matrix with dim (1,4) # - content equals the original vector # Why: # - many downstream functions assume matrix input. # ------------------------------------------------------------------ test_that("convertVector2Matrix converts vector to 1-row matrix", { v <- 1:4 out <- convertVector2Matrix(v) expect_true(is.matrix(out)) expect_equal(dim(out), c(1, 4)) expect_identical(as.integer(out[1, ]), v) }) # ------------------------------------------------------------------ # Test: convertVector2Matrix returns matrix unchanged # Input: # - matrix input # Behaviour: # - should not modify an existing matrix # Expectations: # - identical to the original matrix # Why: # - prevents accidental reshaping when caller already provides a matrix. # ------------------------------------------------------------------ test_that("convertVector2Matrix leaves matrices unchanged", { m <- matrix(1:6, nrow = 2) out <- convertVector2Matrix(m) expect_true(is.matrix(out)) expect_identical(out, m) }) # Tests for createHashTable + getHashValues ------------------------------------ # ------------------------------------------------------------------ # Test: createHashTable stores key-value pairs retrievable via getHashValues # Input: # - keys = c("a","b","c") # - values = list(10,20,30) # Behaviour: # - creates hash and assigns values to keys # Expectations: # - returned object is an environment # - getHashValues returns values in the requested key order # Why: # - mapUniqueTrials relies on retrieval from hashed keys. # ------------------------------------------------------------------ test_that("createHashTable and getHashValues store and retrieve values", { keys <- c("a", "b", "c") vals <- list(10, 20, 30) h <- createHashTable(keys, vals) expect_true(is.environment(h)) out <- getHashValues(c("b", "a"), h) expect_type(out, "list") expect_equal(out, list(20, 10)) }) # Tests for getHashKeys --------------------------------------------------------- # ------------------------------------------------------------------ # Test: getHashKeys uses default x1..xp names if colnames are missing # Input: # - 2x2 matrix without colnames # Behaviour: # - returns character keys "x1:|x2:" per row # Expectations: # - correct key strings for each row # Why: # - hashing must be stable even when matrices lack colnames. # ------------------------------------------------------------------ test_that("getHashKeys uses default x* names without colnames", { x <- matrix(c(1, 2, 3, 4), nrow = 2, byrow = TRUE) out <- getHashKeys(x) expect_type(out, "character") expect_length(out, 2) expect_identical(out[1], "x1:1|x2:2") expect_identical(out[2], "x1:3|x2:4") }) # ------------------------------------------------------------------ # Test: getHashKeys uses existing colnames when present # Input: # - 2x2 matrix with colnames c("A","B") # Behaviour: # - returns "A:|B:" per row # Expectations: # - correct key strings for each row # Why: # - ensures keys remain consistent when names exist. # ------------------------------------------------------------------ test_that("getHashKeys uses colnames when present", { x <- matrix(c(1, 2, 3, 4), nrow = 2, byrow = TRUE) colnames(x) <- c("A", "B") out <- getHashKeys(x) expect_identical(out[1], "A:1|B:2") expect_identical(out[2], "A:3|B:4") }) # Tests for cummulativeMovingAverage ------------------------------------------- # ------------------------------------------------------------------ # Test: cummulativeMovingAverage returns running mean # Input: # - x = c(2,4,6,8) # Behaviour: # - returns cumsum(x) / seq_along(x) # Expectations: # - equals c(2,3,4,5) # Why: # - check for helper used in summaries. # ------------------------------------------------------------------ test_that("cummulativeMovingAverage returns running mean", { x <- c(2, 4, 6, 8) out <- cummulativeMovingAverage(x) expect_equal(out, c(2, 3, 4, 5)) }) # Tests for firstUpper ---------------------------------------------------------- # ------------------------------------------------------------------ # Test: firstUpper capitalizes only the first character # Input: # - "hello", "Hello", "h" # Behaviour: # - replaces first character with uppercase # Expectations: # - correct transformed strings # Why: # - ensures string formatting is deterministic. # ------------------------------------------------------------------ test_that("firstUpper capitalizes first character", { expect_identical(firstUpper("hello"), "Hello") expect_identical(firstUpper("Hello"), "Hello") expect_identical(firstUpper("h"), "H") }) # Tests for getBlankString ------------------------------------------------------ # ------------------------------------------------------------------ # Test: getBlankString returns a string of spaces of requested length # Input: # - length = 0, 3, 10 # Behaviour: # - concatenates that many " " characters # Expectations: # - correct string and nchar # Why: # - used for formatting and alignment in text outputs. # ------------------------------------------------------------------ test_that("getBlankString returns requested number of spaces", { out0 <- getBlankString(0) expect_type(out0, "character") expect_length(out0, 0) out5 <- getBlankString(5) expect_identical(out5, " ") expect_type(out5, "character") expect_length(out5, 1) }) # Tests for getListLevel -------------------------------------------------------- # ------------------------------------------------------------------ # Test: getListLevel returns nesting depth # Input: # - non-list and nested lists # Behaviour: # - returns 0 for non-list; otherwise 1 + depth of first element # Expectations: # - correct depths for 0..3 levels # Why: # - scaleRoundList relies on correct depth to traverse lists. # ------------------------------------------------------------------ test_that("getListLevel returns correct nesting depth", { expect_identical(getListLevel(1), 0) expect_identical(getListLevel(list(1)), 1) expect_identical(getListLevel(list(list(1))), 2) expect_identical(getListLevel(list(list(list(1)))), 3) }) # Tests for getRowIndexOfVectorInMatrix ---------------------------------------- # ------------------------------------------------------------------ # Test: getRowIndexOfVectorInMatrix finds exact matching row # Input: # - matrix with known rows # - vectors equal to those rows # Behaviour: # - returns the indices of rows where all columns match # Expectations: # - correct row index returned; empty integer for no match # Why: # - used to map trial patterns to unique rows. # ------------------------------------------------------------------ test_that("getRowIndexOfVectorInMatrix finds matching rows", { m <- matrix( c(1, 2, 3, 4, 1, 9), ncol = 2, byrow = TRUE ) expect_identical(getRowIndexOfVectorInMatrix(c(1, 2), m), 1L) expect_identical(getRowIndexOfVectorInMatrix(c(3, 4), m), 2L) expect_identical(getRowIndexOfVectorInMatrix(c(1, 9), m), 3L) expect_length(getRowIndexOfVectorInMatrix(c(7, 7), m), 0) }) # ------------------------------------------------------------------ # Test: getRowIndexOfVectorInMatrix errors if vector length != ncol(matrix) # Input: # - vector of wrong length # Behaviour: # - stops with informative error # Expectations: # - error is thrown # Why: # - prevents silent partial matching / wrong indexing. # ------------------------------------------------------------------ test_that("getRowIndexOfVectorInMatrix errors on length mismatch", { m <- matrix(1:4, ncol = 2) expect_error( getRowIndexOfVectorInMatrix(c(1, 2, 3), m), "length of the vector must be equal" ) }) # Tests for invLogit ------------------------------------------------------------ # ------------------------------------------------------------------ # Test: invLogit matches stats::binomial()$linkinv and inverts logit # Input: # - theta = 0 and theta = logit(p) # Behaviour: # - returns inverse logit # Expectations: # - invLogit(0) = 0.5 # - invLogit(logit(p)) = p # Why: # - core link utilities must be numerically correct. # ------------------------------------------------------------------ test_that("invLogit returns correct inverse-logit values", { expect_equal(invLogit(0), 0.5) p <- c(0.1, 0.25, 0.8) expect_equal(invLogit(logit(p)), p, tolerance = 1e-12) }) # ------------------------------------------------------------------ # Test: invLogit validates inputs # Input: # - missing theta, non-numeric theta # Behaviour: # - stops with the documented error # Expectations: # - error thrown # Why: # - input validation prevents silent misuse. # ------------------------------------------------------------------ test_that("invLogit errors for missing or non-numeric input", { expect_error(invLogit(), "Please provide a numeric") expect_error(invLogit("x"), "Please provide a numeric") }) # Tests for logit --------------------------------------------------------------- # ------------------------------------------------------------------ # Test: logit matches stats::binomial()$linkfun and inverts invLogit # Input: # - p = 0.5 and random p values in (0,1) # Behaviour: # - returns log(p/(1-p)) # Expectations: # - logit(0.5) = 0 # - invLogit(logit(p)) = p # Why: # - correctness of link function is fundamental for Berry/ExNex models. # ------------------------------------------------------------------ test_that("logit returns correct log-odds and inverts invLogit", { expect_equal(logit(0.5), 0) p <- c(0.1, 0.25, 0.8) expect_equal(invLogit(logit(p)), p, tolerance = 1e-12) }) # ------------------------------------------------------------------ # Test: logit validates inputs # Input: # - missing p, non-numeric p, p outside [0,1] # Behaviour: # - stops with the documented error # Expectations: # - error thrown # Why: # - prevents invalid probability inputs leaking into models. # ------------------------------------------------------------------ test_that("logit errors for invalid input", { expect_error(logit(), "Please provide a numeric") expect_error(logit("x"), "Please provide a numeric") expect_error(logit(c(-0.1, 0.2)), "Please provide a numeric") expect_error(logit(c(0.2, 1.2)), "Please provide a numeric") }) # Tests for listPerMethod ------------------------------------------------------ # ------------------------------------------------------------------ # Test: listPerMethod reshapes scenario-first lists into method-first lists # Input: # - analyses_list created via simulateScenarios() + performAnalyses() # - estimates_per_method from getEstimates() (method-first) # - scenario-first object reconstructed from estimates_per_method # Behaviour: # - listPerMethod() should convert scenario-first -> method-first # Expectations: # - Output equals estimates_per_method (same methods, same scenarios, same matrices) # Why: # - Verifies listPerMethod performs pure reshaping on real package outputs. # ------------------------------------------------------------------ test_that("listPerMethod reshapes scenario-first lists into method-first lists", { skip_if_not_installed("foreach") foreach::registerDoSEQ() set.seed(123) method_names <- c("pooled", "stratified") target_rates <- c(0.5, 0.5) scen <- simulateScenarios( n_subjects_list = list(c(10, 10), c(20, 20)), response_rates_list = list(c(0.4, 0.4), c(0.6, 0.6)), n_trials = 2 ) analyses <- performAnalyses( scenario_list = scen, evidence_levels = c(0.05, 0.5, 0.95), target_rates = target_rates, method_names = method_names, n_mcmc_iterations = 200, verbose = FALSE ) estimates_per_method <- getEstimates( analyses_list = analyses, point_estimator = "median", alpha_level = 0.10 ) method_names_from_obj <- names(estimates_per_method) scenario_names <- names(estimates_per_method[[1]]) expect_true(all(grepl("^scenario_[0-9]+$", scenario_names))) scenario_first <- vector("list", length = length(scenario_names)) names(scenario_first) <- scenario_names for (s in scenario_names) { scenario_first[[s]] <- lapply( method_names_from_obj, function(m) estimates_per_method[[m]][[s]] ) names(scenario_first[[s]]) <- method_names_from_obj } reshaped <- listPerMethod(scenario_first) expect_setequal(names(reshaped), method_names_from_obj) expect_setequal(names(reshaped[[method_names_from_obj[1]]]), scenario_names) for (m in method_names_from_obj) { for (s in scenario_names) { expect_equal(reshaped[[m]][[s]], estimates_per_method[[m]][[s]]) } } }) # Tests for roundList ----------------------------------------------------------- # ------------------------------------------------------------------ # Test: roundList rounds numerics for nesting levels 1..3 # Input: # - list structures with depths 1, 2, 3 # Behaviour: # - rounds each numeric entry to round_digits # Expectations: # - correct rounded outputs # Why: # - ensures list traversal is correct for supported nesting depths. # ------------------------------------------------------------------ test_that("roundList rounds list entries for levels 1..3", { l1 <- list(1.234, 2.345) expect_equal(roundList(l1, round_digits = 1, list_levels = 1), list(1.2, 2.3)) l2 <- list(a = list(1.234), b = list(2.345)) expect_equal(roundList(l2, round_digits = 2, list_levels = 2), list(a = list(1.23), b = list(2.35))) l3 <- list(a = list(b = list(1.234))) expect_equal(roundList(l3, round_digits = 0, list_levels = 3), list(a = list(b = list(1)))) }) # ------------------------------------------------------------------ # Test: roundList errors for non-numeric entries and unsupported depth # Input: # - list containing non-numeric # - list_levels > 3 # Behaviour: # - stops with informative error # Expectations: # - error thrown # Why: # - avoids silently returning wrong types. # ------------------------------------------------------------------ test_that("roundList errors for non-numerics and unsupported depth", { expect_error(roundList(list("x"), round_digits = 1, list_levels = 1), "must contain numerics") expect_error(roundList(list(1), round_digits = 1, list_levels = 4), "greater than 3") }) # Tests for scaleList ----------------------------------------------------------- # ------------------------------------------------------------------ # Test: scaleList scales numerics for nesting levels 1..3 # Input: # - list structures with depths 1, 2, 3 # Behaviour: # - multiplies each numeric by scale_param # Expectations: # - correct scaled outputs # Why: # - used by scaleRoundList; must behave deterministically. # ------------------------------------------------------------------ test_that("scaleList scales list entries for levels 1..3", { l1 <- list(1, 2) expect_equal(scaleList(l1, scale_param = 10, list_levels = 1), list(10, 20)) l2 <- list(a = list(1), b = list(2)) expect_equal(scaleList(l2, scale_param = 0.5, list_levels = 2), list(a = list(0.5), b = list(1))) l3 <- list(a = list(b = list(2))) expect_equal(scaleList(l3, scale_param = 3, list_levels = 3), list(a = list(b = list(6)))) }) # ------------------------------------------------------------------ # Test: scaleList errors for non-numeric entries and unsupported depth # Input: # - list containing non-numeric # - list_levels > 3 # Behaviour: # - stops with informative error # Expectations: # - error thrown # Why: # - protects against silent type coercion. # ------------------------------------------------------------------ test_that("scaleList errors for non-numerics and unsupported depth", { expect_error(scaleList(list("x"), scale_param = 2, list_levels = 1), "must contain numerics") expect_error(scaleList(list(1), scale_param = 1, list_levels = 4), "greater than 3") }) # Tests for scaleRoundList ------------------------------------------------------ # ------------------------------------------------------------------ # Test: scaleRoundList applies scaling then rounding # Input: # - list of numerics # Behaviour: # - scales by scale_param then rounds to round_digits # Expectations: # - correct scaled+rounded values # Why: # - user-facing helper; expected to behave consistently. # ------------------------------------------------------------------ test_that("scaleRoundList scales then rounds list entries", { skip_if_not_installed("checkmate") l <- list(1.234, 2.345) out <- scaleRoundList(list = l, scale_param = 10, round_digits = 1) expect_equal(out, list(12.3, 23.5)) }) # ------------------------------------------------------------------ # Test: scaleRoundList returns unchanged list if round_digits is NULL # Input: # - list of numerics, round_digits = NULL # Behaviour: # - scales by scale_param, does no rounding when round_digits is NULL # Expectations: # - equals scaled values (or original if scale_param = 1) # Why: # - matches documented behaviour and avoids surprising rounding. # ------------------------------------------------------------------ test_that("scaleRoundList skips rounding when round_digits is NULL", { skip_if_not_installed("checkmate") l <- list(1.234, 2.345) out <- scaleRoundList(list = l, scale_param = 1, round_digits = NULL) expect_equal(out, l) }) # ------------------------------------------------------------------ # Test: scaleRoundList validates inputs # Input: # - missing list / non-list # - invalid scale_param # - invalid round_digits # Behaviour: # - stops with informative errors # Expectations: # - error thrown # Why: # - ensures correct user-facing input checks. # ------------------------------------------------------------------ test_that("scaleRoundList errors for invalid inputs", { skip_if_not_installed("checkmate") expect_error(scaleRoundList(), "Please provide a list") expect_error(scaleRoundList(list = 1), "Please provide a list") l <- list(1.234) expect_error(scaleRoundList(list = l, scale_param = -1)) expect_error(scaleRoundList(list = l, round_digits = -1)) }) # Tests for substrRight --------------------------------------------------------- # ------------------------------------------------------------------ # Test: substrRight returns rightmost n characters # Input: # - x = "abcdef", n = 1,2,6 # Behaviour: # - returns substring from the right # Expectations: # - correct suffixes # Why: # - used for parsing parameter/cohort suffix indices. # ------------------------------------------------------------------ test_that("substrRight returns rightmost n characters", { expect_identical(substrRight("abcdef", 1), "f") expect_identical(substrRight("abcdef", 2), "ef") expect_identical(substrRight("abcdef", 6), "abcdef") })