# Tests for getEstimates ------------------------------------------------------- scenarios_list <- simulateScenarios( n_subjects_list = list(c(10, 20, 30)), response_rates_list = list(c(0.1, 0.2, 3)), # 3 triggers historic-rate branch n_trials = 10 ) analyses_list <- performAnalyses( scenario_list = scenarios_list, target_rates = c(0.5, 0.5, 0.5), calc_differences = matrix(c(3, 2, 2, 1), ncol = 2), # ensures diff cohorts exist n_mcmc_iterations = 100, method_names = "berry" ) outcome <- createTrial( n_subjects = c(10, 20, 30), n_responders = c( 1, 2, 3) ) outcome_analysis <- performAnalyses( scenario_list = outcome, target_rates = c(0.5, 0.5, 0.5), n_mcmc_iterations = 100 ) # ------------------------------------------------------------------- # Test: getEstimates basic structure and contents for simulated scenarios # Input: # - analyses_list: analysis_list with 10 simulated trials, 3 cohorts, diff-cohorts. # Behaviour: # - getEstimates() should return list-per-method of matrices with posterior summaries, # Bias and MSE for response-rate parameters and diff parameters. # Expectations: # - Output is a non-empty list. # - First inner element is a matrix with columns Mean, SD, 2.5%, 50%, 97.5%, Bias, MSE. # - Row names contain p_* for cohorts and "diff" rows for differences. # Why: # - This is the main “happy path”; other tests rely on this shape. # ------------------------------------------------------------------- test_that("getEstimates works for simulated scenarios and returns sensible structure", { res <- getEstimates(analyses_list) expect_type(res, "list") expect_true(length(res) > 0) first_obj <- res[[1]] if (is.list(first_obj)) { first_mat <- first_obj[[1]] } else { first_mat <- first_obj } expect_true(is.matrix(first_mat)) expect_true(all(c("Mean", "SD", "2.5%", "50%", "97.5%", "Bias", "MSE") %in% colnames(first_mat))) expect_true(any(grepl("^p_", rownames(first_mat)))) expect_true(any(grepl("diff", rownames(first_mat)))) }) # ------------------------------------------------------------------- # Test: getEstimates with add_parameters and historic-rate handling # Input: # - Same analyses_list as above. # - add_parameters = c("mu", "tau", "w_1", "w_2", "w_3"). # Behaviour: # - Additional model parameters are appended as extra rows. # - For historic cohorts (rr <= 0 or >= 1) true_rr is derived as responders/n_subjects. # - Bias and MSE are computed only for p_* rows, not for mu/tau/w_*. # Expectations: # - p_3 row exists and its Bias + true_rr_used equals the median ("50%") estimate. # - Column structure identical with and without add_parameters. # - Extra rows correspond to mu/tau/w_* and have NA for Bias/MSE. # Why: # - Ensures correct handling of historic cohorts and extra parameters. # ------------------------------------------------------------------- test_that("additional parameters are added and have NA bias/MSE", { res_base <- getEstimates(analyses_list) base_obj <- res_base[[1]] base_mat <- if (is.list(base_obj)) base_obj[[1]] else base_obj res_add <- getEstimates( analyses_list = analyses_list, add_parameters = c("mu", "tau", "w_1", "w_2", "w_3") ) add_obj <- res_add[[1]] add_mat <- if (is.list(add_obj)) add_obj[[1]] else add_obj p3_row <- grep("^p_3$", rownames(add_mat)) expect_length(p3_row, 1) bias_p3 <- add_mat[p3_row, "Bias"] median_p3 <- add_mat[p3_row, "50%"] true_rr_raw <- analyses_list[[1]]$scenario_data$response_rates n_subj <- analyses_list[[1]]$scenario_data$n_subjects[1, ] expected_true_rr3 <- true_rr_raw[3] / n_subj[[3]] point_estimate3 <- bias_p3 + expected_true_rr3 expect_equal(point_estimate3, median_p3) expect_identical(colnames(base_mat), colnames(add_mat)) extra_rows <- setdiff(rownames(add_mat), rownames(base_mat)) expect_true(length(extra_rows) > 0) expect_true(all(grepl("mu|tau|w_", extra_rows))) expect_true(all(is.na(add_mat[extra_rows, "Bias"]))) expect_true(all(is.na(add_mat[extra_rows, "MSE"]))) }) # ------------------------------------------------------------------- # Test: getEstimates for single-trial outcome # Input: # - outcome_analysis: analysis_list from createTrial() (one trial only). # Behaviour: # - With n_trials = 1, getEstimates() should *not* compute Bias/MSE, # only posterior summaries. # Expectations: # - Returned inner matrix has columns exactly: Mean, SD, 2.5%, 50%, 97.5%. # Why: # - Distinguishes between simulation-based metrics and outcome-only analyses. # ------------------------------------------------------------------- test_that("single-trial outcome returns only posterior summaries (no bias/MSE)", { res_single <- getEstimates(outcome_analysis) single_obj <- res_single[[1]] single_mat <- if (is.list(single_obj)) single_obj[[1]] else single_obj expect_true(is.matrix(single_mat)) expect_identical( colnames(single_mat), c("Mean", "SD", "2.5%", "50%", "97.5%") ) }) # ------------------------------------------------------------------- # Test: validation of alpha_level and add_parameters in getEstimates # Input: # - analyses_list from the main fixture. # Behaviour: # - alpha_level must be in (0, 1) and must correspond to stored quantiles. # - add_parameters must exist in at least one method’s posterior draws. # Expectations: # - alpha_level = 0.07 (not among stored quantiles) -> error. # - add_parameters = "totally_unknown_param" -> error with appropriate message. # Why: # - Ensures defensive checks for user-supplied quantiles and parameter names. # ------------------------------------------------------------------- test_that("alpha_level and add_parameters are validated correctly", { expect_error( getEstimates(analyses_list, alpha_level = 0.07) ) expect_error( getEstimates(analyses_list, add_parameters = c("totally_unknown_param")) ) }) # ------------------------------------------------------------------- # Test: point_estimator behaviour and basic type checks in getEstimates # Input: # - analyses_list, point_estimator = "median" and "mean". # Behaviour: # - Both estimators should work and yield same matrix shape. # - An invalid point_estimator should raise an error. # - Non-analysis_list input should raise an error. # Expectations: # - dim() and colnames() identical for median vs mean. # - point_estimator = "mode" -> error. # - analyses_list = list(a = 1) -> error on class. # Why: # - Confirms configurability of the estimator and robust input validation. # ------------------------------------------------------------------- test_that("point_estimator argument is respected and input type is validated", { res_median <- getEstimates(analyses_list, point_estimator = "median") res_mean <- getEstimates(analyses_list, point_estimator = "mean") med_obj <- res_median[[1]] mean_obj <- res_mean[[1]] med_mat <- if (is.list(med_obj)) med_obj[[1]] else med_obj mean_mat <- if (is.list(mean_obj)) mean_obj[[1]] else mean_obj expect_identical(dim(med_mat), dim(mean_mat)) expect_identical(colnames(med_mat), colnames(mean_mat)) expect_error( getEstimates(analyses_list, point_estimator = "mode") ) expect_error( getEstimates(list(a = 1)) ) }) # ------------------------------------------------------------------- # Additional explicit validation tests for getEstimates: # - alpha_level outside (0,1) # - alpha_level with unavailable quantiles # - add_parameters not found # - basic structure for single-trial # - duplicate explicit checks for invalid point_estimator and analyses_list # ------------------------------------------------------------------- test_that("throws error for invalid alpha_level (outside 0,1)", { expect_error(getEstimates(analyses_list, alpha_level = 1.5), "alpha_level") }) test_that("throws error if alpha_level quantiles not available", { expect_error( getEstimates(analyses_list, alpha_level = 0.123), "must be among the stored quantiles" ) }) test_that("throws error if add_parameters not found in any method", { expect_error( getEstimates(analyses_list, add_parameters = c("nonexistent")), "do not occur" ) }) test_that("works for single trial outcome (basic check)", { result <- getEstimates(outcome_analysis) expect_type(result, "list") }) test_that("invalid point_estimator throws error explicitly", { expect_error( getEstimates(analyses_list, point_estimator = "mode"), "point_estimator" ) }) test_that("invalid analyses_list class throws error explicitly", { expect_error( getEstimates(list()), "analyses_list" ) }) # Tests for getGoDecisions / getGoProbabilities / print.decision_list ---------- set.seed(123) scenarios_list <- simulateScenarios( n_subjects_list = list(c(10, 20, 30)), response_rates_list = list(c(0.3, 0.5, 0.7)), n_trials = 10 ) analyses_list <- performAnalyses( scenario_list = scenarios_list, target_rates = c(0.3, 0.3, 0.3), n_mcmc_iterations = 100 ) default_cohorts <- c("p_1", "p_2", "p_3") # ------------------------------------------------------------------- # Test: getGoDecisions – analyses_list must have correct class # Input: # - analyses_list = list() (wrong type) # Behaviour: # - checkmate::assertClass enforces class 'analysis_list'. # Expectations: # - Error mentioning 'analysis_list' is thrown. # Why: # - Prevents silent misuse with arbitrary lists. # ------------------------------------------------------------------- test_that("getGoDecisions: errors if analyses_list is not of class 'analysis_list'", { expect_error( getGoDecisions( analyses_list = list(), cohort_names = "p_1", evidence_levels = 0.5, boundary_rules = quote(c(TRUE)) ), "analysis_list" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – evidence_levels and boundary_rules required # Input: # - Missing evidence_levels or missing boundary_rules. # Behaviour: # - Custom error messages if either argument is missing. # Expectations: # - Missing evidence_levels -> error message refers to evidence_levels. # - Missing boundary_rules -> error message refers to boundary_rules. # Why: # - These are essential pieces of the decision rule definition. # ------------------------------------------------------------------- test_that("getGoDecisions: errors when evidence_levels or boundary_rules are missing", { expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = "p_1", boundary_rules = quote(c(TRUE)) ), "evidence_levels" ) expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = "p_1", evidence_levels = 0.5 ), "boundary_rules" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – invalid cohort_names # Input: # - cohort_names = "invalid" (not present in posterior columns). # Behaviour: # - assertSubset ensures cohort_names correspond to posterior parameters. # Expectations: # - Error referencing 'cohort_names'. # Why: # - Prevents mis-specification of cohort parameter names. # ------------------------------------------------------------------- test_that("getGoDecisions: errors if cohort_names are not valid posterior parameters", { expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = "invalid", evidence_levels = 0.5, boundary_rules = quote(c(TRUE)) ), "cohort_names" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – overall_min_gos must be positive integer # Input: # - overall_min_gos = 0L. # Behaviour: # - assertInt(overall_min_gos, lower = 1) rejects non-positive values. # Expectations: # - Error referencing 'overall_min_gos'. # Why: # - The minimum number of cohort-wise Go decisions must be >= 1. # ------------------------------------------------------------------- test_that("getGoDecisions: errors if overall_min_gos is not a positive integer", { expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = "p_1", evidence_levels = 0.5, boundary_rules = quote(c(TRUE)), overall_min_gos = 0L ), "overall_min_gos" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – numeric evidence_levels in (0,1) # Input: # - evidence_levels = c(-0.1, 0.5, 1.3). # Behaviour: # - check.evidence.levels enforces all levels are in (0,1). # Expectations: # - Error is thrown for invalid vector. # Why: # - Evidence thresholds must be valid probabilities. # ------------------------------------------------------------------- test_that("getGoDecisions: numeric evidence_levels outside (0,1) cause an error", { expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(-0.1, 0.5, 1.3), boundary_rules = quote(c(TRUE, TRUE, TRUE)) ) ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – list evidence_levels with valid entries # Input: # - evidence_levels as list of numeric vectors in (0,1). # Behaviour: # - check.evidence.levels is applied to each list element. # Expectations: # - No error is thrown for valid list structure. # Why: # - Supports method-specific evidence levels. # ------------------------------------------------------------------- test_that("getGoDecisions: list evidence_levels all elements valid", { ev_list_ok <- list( rep(0.5, length(default_cohorts)), rep(0.5, length(default_cohorts)) ) expect_silent( getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = ev_list_ok, boundary_rules = quote(c(TRUE, TRUE, TRUE)) ) ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – non-list boundary_rules must be a language object # Input: # - boundary_rules = 1 (numeric, not a call). # Behaviour: # - check_boundary_rules rejects non-language boundary rules. # Expectations: # - Error referencing 'boundary_rules'. # Why: # - Decision rules must be expressed as quoted R expressions. # ------------------------------------------------------------------- test_that("getGoDecisions: non-list boundary_rules must be a language object", { expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = 1 ), "boundary_rules" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – non-list boundary_rules must use c(...) # Input: # - boundary_rules = quote(list(TRUE, TRUE, TRUE)). # Behaviour: # - The call head must be `c`, so list(...) is rejected. # Expectations: # - Error referencing 'boundary_rules'. # Why: # - The internal logic expects a c(...) vector of logical decisions. # ------------------------------------------------------------------- test_that("getGoDecisions: non-list boundary_rules must start with c()", { expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = quote(list(TRUE, TRUE, TRUE)) ), "boundary_rules" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – non-list boundary_rules length must match #cohorts # Input: # - 3 cohorts but boundary_rules = quote(c(TRUE, TRUE)). # Behaviour: # - Length mismatch triggers error in check_boundary_rules. # Expectations: # - Error referencing 'boundary_rules'. # Why: # - Each cohort must have one logical decision rule. # ------------------------------------------------------------------- test_that("getGoDecisions: non-list boundary_rules must have one entry per cohort", { expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = quote(c(TRUE, TRUE)) ), "boundary_rules" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – list boundary_rules elements must be language # Input: # - boundary_rules = list(123). # Behaviour: # - Each element of the list must be a call; a numeric element is invalid. # Expectations: # - Error referencing 'boundary_rules'. # Why: # - Supports multiple methods with method-specific decision rules. # ------------------------------------------------------------------- test_that("getGoDecisions: list boundary_rules each element must be a language object", { br <- list(123) expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = br ), "boundary_rules" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – list boundary_rules head must be c() # Input: # - boundary_rules = list(quote(list(TRUE, TRUE, TRUE))). # Behaviour: # - Each element must start with c(...); list(...) is rejected. # Expectations: # - Error referencing 'boundary_rules'. # Why: # - Same reasoning as the non-list case, but in list form. # ------------------------------------------------------------------- test_that("getGoDecisions: list boundary_rules each element must start with c()", { br <- list(quote(list(TRUE, TRUE, TRUE))) expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = br ), "boundary_rules" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – list boundary_rules length must match #cohorts # Input: # - boundary_rules = list(quote(c(TRUE, TRUE))) for 3 cohorts. # Behaviour: # - Length mismatch per element triggers error. # Expectations: # - Error referencing 'boundary_rules'. # Why: # - Ensures consistent vector length across all cohorts. # ------------------------------------------------------------------- test_that("getGoDecisions: list boundary_rules must match number of cohorts", { br <- list(quote(c(TRUE, TRUE))) expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = br ), "boundary_rules" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – boundary_rules list length vs method_names # Input: # - boundary_rules list longer than number of methods. # Behaviour: # - Length check should reject boundary_rules with length > length(method_names). # Expectations: # - Error referencing 'boundary_rules'. # Why: # - Each method can have at most one decision rule expression. # ------------------------------------------------------------------- test_that("errors if boundary_rules list is longer than method_names", { m <- analyses_list[[1]]$analysis_parameters$method_names coh <- c("p_1", "p_2", "p_3") ev <- c(0.5, 0.5, 0.5) br <- rep(list(quote(c(TRUE, TRUE, TRUE))), length(m) + 1L) expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = coh, evidence_levels = ev, boundary_rules = br ), "boundary_rules" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – evidence_levels list length vs method_names # Input: # - evidence_levels list longer than number of methods. # Behaviour: # - Length check should reject gamma_levels with length > length(method_names). # Expectations: # - Error referencing 'evidence_levels'. # Why: # - Each method can have at most one vector of evidence thresholds. # ------------------------------------------------------------------- test_that("errors if evidence_levels list is longer than method_names", { m <- analyses_list[[1]]$analysis_parameters$method_names coh <- c("p_1", "p_2", "p_3") ev_vec <- c(0.5, 0.5, 0.5) ev <- rep(list(ev_vec), length(m) + 1L) expect_error( getGoDecisions( analyses_list = analyses_list, cohort_names = coh, evidence_levels = ev, boundary_rules = quote(c(TRUE, TRUE, TRUE)) ), "evidence_levels" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – single boundary_rules expression recycled # Input: # - boundary_rules = quote(c(TRUE, TRUE, TRUE)) and multiple methods. # Behaviour: # - Single expression is replicated for each method internally. # Expectations: # - decision_rules$boundary_rules has length = #methods, # each element identical to the original rule. # Why: # - Confirms convenience recycling logic. # ------------------------------------------------------------------- test_that("single boundary_rules expression is recycled to all methods", { m <- analyses_list[[1]]$analysis_parameters$method_names coh <- c("p_1", "p_2", "p_3") ev <- c(0.5, 0.5, 0.5) rule <- quote(c(TRUE, TRUE, TRUE)) dec <- getGoDecisions( analyses_list = analyses_list, cohort_names = coh, evidence_levels = ev, boundary_rules = rule ) br <- dec[[1]]$decision_rules$boundary_rules expect_identical(length(br), length(m)) for (i in seq_along(br)) { expect_true(identical(br[[i]], rule)) } }) # ------------------------------------------------------------------- # Test: getGoDecisions – single evidence_levels vector recycled # Input: # - evidence_levels = c(0.5, 0.5, 0.5) and multiple methods. # Behaviour: # - Single numeric vector is replicated per method. # Expectations: # - decision_rules$gamma_levels has length = #methods, # each element equal to the original vector. # Why: # - Confirms convenience recycling for evidence thresholds. # ------------------------------------------------------------------- test_that("single evidence_levels vector is recycled to all methods", { m <- analyses_list[[1]]$analysis_parameters$method_names coh <- c("p_1", "p_2", "p_3") ev <- c(0.5, 0.5, 0.5) dec <- getGoDecisions( analyses_list = analyses_list, cohort_names = coh, evidence_levels = ev, boundary_rules = quote(c(TRUE, TRUE, TRUE)) ) gamma <- dec[[1]]$decision_rules$gamma_levels expect_identical(length(gamma), length(m)) for (i in seq_along(gamma)) { expect_equal(gamma[[i]], ev) } }) # ------------------------------------------------------------------- # Test: getGoDecisions – method names consistent across scenarios # Input: # - analyses_list with identical method_names per scenario. # Behaviour: # - Method name matrix check passes; decisions are computed. # Expectations: # - Returned object is of class decision_list. # Why: # - Sanity check for the main use-case of multiple scenarios with same methods. # ------------------------------------------------------------------- test_that("getGoDecisions: succeeds when all scenarios use identical method_names", { res <- getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = quote(c(TRUE, TRUE, TRUE)) ) expect_s3_class(res, "decision_list") }) # ------------------------------------------------------------------- # Test: getGoDecisions – error when scenarios use different methods # Input: # - analyses_list where scenario_2 has method_names reversed vs scenario_1. # Behaviour: # - Method-name consistency check fails. # Expectations: # - Error with message “analysed with different methods”. # Why: # - Mixed-method setups per scenario are not supported. # ------------------------------------------------------------------- test_that("getGoDecisions: errors when scenarios were analysed with different methods", { bad <- analyses_list bad$scenario_2 <- bad$scenario_1 bad$scenario_2$analysis_parameters$method_names <- rev(bad$scenario_2$analysis_parameters$method_names) expect_error( getGoDecisions( analyses_list = bad, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = quote(c(TRUE, TRUE, TRUE)) ), "analysed with different methods" ) }) # ------------------------------------------------------------------- # Test: getGoDecisions – structure of decision_list and 'overall' column # Input: # - Valid analyses_list, three cohorts, evidence levels. # Behaviour: # - getGoDecisions returns decision_list with: # - decisions_list per method/scenario including 'overall' and cohort columns. # - decision_rules storing cohort_names and gamma_levels. # Expectations: # - 'overall' column exists. # - At least one cohort-level decision column exists. # - decision_rules$cohort_names identical to input. # - stored gamma levels contain the supplied values. # Why: # - Verifies structural contract of decision_list objects. # ------------------------------------------------------------------- test_that("getGoDecisions: returns decision_list with overall and cohort decisions", { decisions <- getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = quote(c(TRUE, TRUE, TRUE)) ) scen1 <- decisions[[1]] m_dec <- as.matrix(scen1$decisions_list[[1]]) expect_true("overall" %in% colnames(m_dec)) cohort_cols <- setdiff(colnames(m_dec), "overall") expect_true(length(cohort_cols) >= 1) expect_identical(scen1$decision_rules$cohort_names, default_cohorts) stored_gamma_flat <- unlist(scen1$decision_rules$gamma_levels, use.names = FALSE) expect_true(all(c(0.5, 0.5, 0.8) %in% stored_gamma_flat)) }) # ------------------------------------------------------------------- # Test: getGoDecisions – semantics of overall_min_gos = 1 # Input: # - overall_min_gos = 1, boundary_rules always TRUE. # Behaviour: # - overall decision is TRUE if at least one cohort is TRUE. # Expectations: # - 'overall' column equals row-wise indicator of ≥1 TRUE among cohorts. # Why: # - Checks threshold behaviour for overall Go decision. # ------------------------------------------------------------------- test_that("overall_min_gos = 1 means overall Go if at least one cohort is Go", { dec <- getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = rep(0.5, length(default_cohorts)), boundary_rules = quote(c(TRUE, TRUE, TRUE)), overall_min_gos = 1L ) m <- as.matrix(dec[[1]]$decisions_list[[1]]) coh_cols <- setdiff(colnames(m), "overall") expect_true(length(coh_cols) >= 1) coh <- m[, coh_cols, drop = FALSE] > 0 overall_calc <- apply(coh, 1, function(x) sum(x) >= 1L) overall <- as.logical(m[, "overall"]) expect_identical(overall, overall_calc) }) # ------------------------------------------------------------------- # Test: getGoDecisions – semantics of overall_min_gos = 2 # Input: # - overall_min_gos = 2, boundary_rules always TRUE. # Behaviour: # - overall decision is TRUE if at least two cohorts are TRUE. # Expectations: # - 'overall' column equals row-wise indicator of ≥2 TRUE among cohorts. # Why: # - Confirms threshold is correctly applied for higher overall minimum. # ------------------------------------------------------------------- test_that("overall_min_gos = 2 means overall Go if at least two cohorts are Go", { dec <- getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = rep(0.5, length(default_cohorts)), boundary_rules = quote(c(TRUE, TRUE, TRUE)), overall_min_gos = 2L ) m <- as.matrix(dec[[1]]$decisions_list[[1]]) coh_cols <- setdiff(colnames(m), "overall") expect_true(length(coh_cols) >= 1) coh <- m[, coh_cols, drop = FALSE] > 0 overall_calc <- apply(coh, 1, function(x) sum(x) >= 2L) overall <- as.logical(m[, "overall"]) expect_identical(overall, overall_calc) }) # ------------------------------------------------------------------- # Test: getGoDecisions – no resurrection of previously stopped cohorts # Input: # - analyses_list with scenario_data$previous_analyses$go_decisions present. # Behaviour: # - New go_decisions are multiplied by previous_gos, so a previously stopped # cohort cannot become Go again. # Expectations: # - No cell where new_go == TRUE and previous_go == FALSE. # Why: # - Implements the intended “no resurrection” rule across analyses. # ------------------------------------------------------------------- test_that("getGoDecisions: previous go_decisions prevent resurrection of stopped cohorts", { if (is.null(analyses_list[[1]]$scenario_data$previous_analyses)) { skip("previous_analyses not available in scenario_data") } decisions <- getGoDecisions( analyses_list = analyses_list, cohort_names = default_cohorts, evidence_levels = c(0.5, 0.5, 0.8), boundary_rules = quote(c(TRUE, TRUE, TRUE)) ) new_mat <- as.matrix(decisions[[1]]$decisions_list[[1]]) new_cohcols <- setdiff(colnames(new_mat), "overall") new_gos <- new_mat[, new_cohcols, drop = FALSE] > 0 prev_mat <- analyses_list[[1]]$scenario_data$previous_analyses$go_decisions prev_gos <- as.matrix(prev_mat[, -1, drop = FALSE]) > 0 expect_identical(dim(prev_gos), dim(new_gos)) resurrected <- new_gos & !prev_gos expect_false(any(resurrected)) }) # Tests for getGoProbabilities ------------------------------------------------- set.seed(456) scenarios_list_prob <- simulateScenarios( n_subjects_list = list(c(10, 20)), response_rates_list = list(rep(0.9, 2)), n_trials = 10 ) analyses_list_prob <- performAnalyses( scenario_list = scenarios_list_prob, target_rates = rep(0.5, 2), n_mcmc_iterations = 100 ) prob_cohorts <- c("p_1", "p_2") go_decisions_list <- getGoDecisions( analyses_list = analyses_list_prob, cohort_names = prob_cohorts, evidence_levels = c(0.5, 0.5), boundary_rules = quote(c(x[1] > 0.8, x[2] > 0.6)) ) nogo_decisions_list <- getGoDecisions( analyses_list = analyses_list_prob, cohort_names = prob_cohorts, evidence_levels = c(0.5, 0.5), boundary_rules = quote(c(x[1] < 0.5, x[2] < 0.3)) ) # ------------------------------------------------------------------- # Test: getGoProbabilities – go_decisions_list must be decision_list # Input: # - go_decisions_list = list(). # Behaviour: # - assertClass(go_decisions_list, "decision_list") should fail. # Expectations: # - Error mentioning 'go_decisions_list'. # Why: # - Ensures user passes the correct object type. # ------------------------------------------------------------------- test_that("getGoProbabilities: errors if go_decisions_list is not a decision_list", { expect_error( getGoProbabilities(go_decisions_list = list()), "go_decisions_list" ) }) # ------------------------------------------------------------------- # Test: getGoProbabilities – nogo_decisions_list must be decision_list or NULL # Input: # - nogo_decisions_list = list(). # Behaviour: # - assertClass(..., null.ok = TRUE) fails when object is not decision_list. # Expectations: # - Error mentioning 'decision_list'. # Why: # - Prevents malformed NoGo objects. # ------------------------------------------------------------------- test_that("getGoProbabilities: errors if nogo_decisions_list has wrong class", { expect_error( getGoProbabilities( go_decisions_list = go_decisions_list, nogo_decisions_list = list() ), "decision_list" ) }) # ------------------------------------------------------------------- # Test: getGoProbabilities – Go/NoGo matrices must have same dimensions # Input: # - bad_nogo with first method’s matrix missing a column. # Behaviour: # - Dimension check should fail if the shapes differ. # Expectations: # - Error is thrown (no specific regex needed). # Why: # - Go and NoGo must align element-wise. # ------------------------------------------------------------------- test_that("getGoProbabilities: errors if Go and NoGo matrices have different dimensions", { bad_nogo <- nogo_decisions_list bad_nogo[[1]]$decisions_list[[1]] <- bad_nogo[[1]]$decisions_list[[1]][, -1, drop = FALSE] expect_error( getGoProbabilities( go_decisions_list = go_decisions_list, nogo_decisions_list = bad_nogo ) ) }) # ------------------------------------------------------------------- # Test: getGoProbabilities – Go-only case structure # Input: # - go_decisions_list, nogo_decisions_list = NULL. # Behaviour: # - Returns list-per-method of matrices with single row "Go". # Expectations: # - probs is a list of lists of matrices. # - First matrix has rownames "Go" and at least one column. # Why: # - Checks basic output shape in the simpler Go-only mode. # ------------------------------------------------------------------- test_that("getGoProbabilities: Go-only call returns list-of-lists of matrices", { probs <- getGoProbabilities(go_decisions_list) expect_type(probs, "list") expect_true(all(sapply(probs, is.list))) first_method <- names(probs)[1] first_scenario <- names(probs[[first_method]])[1] mat <- probs[[first_method]][[first_scenario]] expect_true(is.matrix(mat)) expect_identical(rownames(mat), "Go") expect_true(ncol(mat) >= 1) }) # ------------------------------------------------------------------- # Test: getGoProbabilities – Go row equals column means of go_decisions # Input: # - go_decisions_list with full decision matrices. # Behaviour: # - Row "Go" is defined as colMeans(go_decisions). # Expectations: # - For each method/scenario, mat_prob["Go", ] == colMeans(go_mat). # Why: # - Validates the probability computation logic. # ------------------------------------------------------------------- test_that("getGoProbabilities: Go row equals column means of go_decisions", { probs <- getGoProbabilities(go_decisions_list) method_names <- names(go_decisions_list[[1]]$decisions_list) scenario_names <- names(go_decisions_list) for (m in method_names) { for (s in scenario_names) { mat_prob <- probs[[m]][[s]] go_mat <- go_decisions_list[[s]]$decisions_list[[m]] expected <- colMeans(go_mat) expect_equal( mat_prob["Go", ], expected, info = paste("Mismatch in method", m, "scenario", s) ) } } }) # ------------------------------------------------------------------- # Test: getGoProbabilities – Go and NoGo rows match colMeans of inputs # Input: # - go_decisions_list and nogo_decisions_list for same scenarios. # Behaviour: # - "Go" row = colMeans(go_mat), "NoGo" row = colMeans(nogo_mat). # Expectations: # - Equality holds for all methods and scenarios. # Why: # - Confirms correct aggregation of both Go and NoGo decisions. # ------------------------------------------------------------------- test_that("getGoProbabilities: Go and NoGo rows match colMeans of input decisions", { probs <- getGoProbabilities( go_decisions_list = go_decisions_list, nogo_decisions_list = nogo_decisions_list ) method_names <- names(go_decisions_list[[1]]$decisions_list) scenario_names <- names(go_decisions_list) for (m in method_names) { for (s in scenario_names) { mat_prob <- probs[[m]][[s]] go_mat <- go_decisions_list[[s]]$decisions_list[[m]] nogo_mat <- nogo_decisions_list[[s]]$decisions_list[[m]] expected_go <- colMeans(go_mat) expected_nogo <- colMeans(nogo_mat) expect_equal( mat_prob["Go", ], expected_go, info = paste("Go row mismatch in method", m, "scenario", s) ) expect_equal( mat_prob["NoGo", ], expected_nogo, info = paste("NoGo row mismatch in method", m, "scenario", s) ) } } }) # ------------------------------------------------------------------- # Test: getGoProbabilities – column sums to 1 with Go/NoGo/Consider # Input: # - go_decisions_list and nogo_decisions_list. # Behaviour: # - "Consider" row is computed as 1 - p(Go) - p(NoGo), so column sums = 1. # Expectations: # - For each scenario, colSums(matrix) == 1 exactly. # Why: # - Sanity check that probabilities are normalized. # ------------------------------------------------------------------- test_that("getGoProbabilities: columns sum to 1 when NoGo decisions are provided", { probs <- getGoProbabilities( go_decisions_list = go_decisions_list, nogo_decisions_list = nogo_decisions_list ) first_method <- names(probs)[1] for (scen_name in names(probs[[first_method]])) { mat <- probs[[first_method]][[scen_name]] col_sums <- colSums(mat) expect_true( all(abs(col_sums - 1) == 0), info = paste("Column sums not 1 for scenario", scen_name) ) } }) # ------------------------------------------------------------------- # Test: getGoProbabilities – overlapping Go and NoGo decisions # Input: # - Modified go_decisions_list and nogo_decisions_list where at least one # cell is TRUE in both. # Behaviour: # - Overlap check should throw an error. # Expectations: # - Error containing “both go and nogo decisions”. # Why: # - A trial cannot be simultaneously Go and NoGo in the same cell. # ------------------------------------------------------------------- test_that("getGoProbabilities: errors if any decision is both Go and NoGo", { overlap_go <- go_decisions_list overlap_nogo <- nogo_decisions_list scen1_name <- names(overlap_go)[1] meth1_name <- names(overlap_go[[scen1_name]]$decisions_list)[1] overlap_go[[scen1_name]]$decisions_list[[meth1_name]][1, 1] <- TRUE overlap_nogo[[scen1_name]]$decisions_list[[meth1_name]][1, 1] <- TRUE expect_error( getGoProbabilities( go_decisions_list = overlap_go, nogo_decisions_list = overlap_nogo ), "both go and nogo decisions" ) }) # Tests for print.decision_list ------------------------------------------------ set.seed(789) scenarios_print <- simulateScenarios( n_subjects_list = list(c(10, 20), c(10, 20)), response_rates_list = list(c(0.3, 0.5), c(0.4, 0.6)), n_trials = 5 ) analyses_print <- performAnalyses( scenario_list = scenarios_print, target_rates = c(0.3, 0.3), n_mcmc_iterations = 80 ) coh_print <- c("p_1", "p_2") decisions_dl <- getGoDecisions( analyses_list = analyses_print, cohort_names = coh_print, evidence_levels = c(0.5, 0.5), boundary_rules = quote(c(TRUE, TRUE)) ) n_scenarios <- length(decisions_dl) n_methods <- length(decisions_dl[[1]]$decisions_list) # ------------------------------------------------------------------- # Test: print.decision_list – header shows scenario/method counts # Input: # - decisions_dl with known number of scenarios and methods. # Behaviour: # - Header line prints “decision_list of N scenario(s) with M method(s)”. # Expectations: # - Output contains the constructed header pattern. # Why: # - Confirms informative summary in the print method. # ------------------------------------------------------------------- test_that("print.decision_list: header shows correct number of scenarios and methods", { header_pattern <- sprintf( "decision_list of %d scenario%s with %d method%s", n_scenarios, ifelse(n_scenarios == 1, "", "s"), n_methods, ifelse(n_methods == 1, "", "s") ) expect_output( print(decisions_dl), header_pattern ) }) # ------------------------------------------------------------------- # Test: print.decision_list – scenario sections appear # Input: # - decisions_dl with scenario names. # Behaviour: # - For each scenario, the print method outputs “ - ”. # Expectations: # - Each scenario name appears at least once in captured output. # Why: # - Confirms per-scenario blocks are printed. # ------------------------------------------------------------------- test_that("print.decision_list: prints a section for each scenario", { scen_names <- names(decisions_dl) expect_true(length(scen_names) >= 1) output <- capture.output(print(decisions_dl)) for (nm in scen_names) { expect_true( any(grepl(paste0("\\b", nm, "\\b"), output)), info = paste("Scenario name", nm, "not found in printed output") ) } }) # ------------------------------------------------------------------- # Test: print.decision_list – method labels appear # Input: # - decisions_dl with method_names present. # Behaviour: # - Row labels are built from method_names via firstUpper(). # Expectations: # - Each method name (firstUpper) appears somewhere in output. # Why: # - Verifies mapping from internal method_names to printed labels. # ------------------------------------------------------------------- test_that("print.decision_list: each method name appears in printed matrix rows", { method_names <- names(decisions_dl[[1]]$decisions_list) expect_true(length(method_names) >= 1) output <- capture.output(print(decisions_dl)) for (m in method_names) { m_upper <- paste0(toupper(substr(m, 1, 1)), substr(m, 2, nchar(m))) expect_true( any(grepl(m_upper, output, fixed = TRUE)), info = paste("Method label", m_upper, "not found in printed output") ) } }) # ------------------------------------------------------------------- # Test: print.decision_list – digits argument works # Input: # - digits = 1 and digits = 4. # Behaviour: # - Numeric entries are rounded with the given digits; no error. # Expectations: # - Both calls produce output containing “decision_list of”. # Why: # - Ensures digits parameter is respected but does not break printing. # ------------------------------------------------------------------- test_that("print.decision_list: digits argument is accepted and does not error", { expect_output( print(decisions_dl, digits = 1), "decision_list of" ) expect_output( print(decisions_dl, digits = 4), "decision_list of" ) }) # ------------------------------------------------------------------- # Test: print.decision_list – handles non-NULL decision_rules # Input: # - decisions_dl with decision_rules present. # Behaviour: # - Internal block rewrites boundary_rules for pretty printing. # Expectations: # - Printing completes without error and includes header. # Why: # - Confirms the complex decision_rules formatting branch works. # ------------------------------------------------------------------- test_that("print.decision_list: handles non-NULL decision_rules without error", { expect_output( print(decisions_dl), "decision_list of" ) }) # ------------------------------------------------------------------- # Test: print.decision_list – works when decision_rules are NULL # Input: # - decisions_dl with decision_rules removed. # Behaviour: # - Skips decision_rules formatting and prints summary based only on decisions. # Expectations: # - Header is printed as usual, no error occurs. # Why: # - Ensures robust printing even if decision_rules slot is absent. # ------------------------------------------------------------------- test_that("print.decision_list: works when decision_rules are NULL", { dl_no_rules <- decisions_dl for (i in seq_along(dl_no_rules)) { dl_no_rules[[i]]$decision_rules <- NULL } header_pattern <- sprintf( "decision_list of %d scenario%s with %d method%s", n_scenarios, ifelse(n_scenarios == 1, "", "s"), n_methods, ifelse(n_methods == 1, "", "s") ) expect_output( print(dl_no_rules), header_pattern ) }) # ------------------------------------------------------------------- # Test: print.decision_list – one row per method per scenario # Input: # - decisions_dl with >1 scenario and potentially multiple methods. # Behaviour: # - For each scenario, mat_out = rbind over methods; rownames become # “ - ”. # Expectations: # - Total number of lines starting with “ - ” equals n_scenarios * #methods. # Why: # - Confirms the aggregation over methods matches the printed labels. # ------------------------------------------------------------------- test_that("print.decision_list: for multiple methods, one row per method is printed per scenario", { go_probs <- getGoProbabilities(decisions_dl) scenario_index <- 1L mat_ref <- do.call( rbind, lapply(go_probs, function(y) y[[scenario_index]]) ) output <- capture.output(print(decisions_dl)) method_label_lines <- grep("^ - ", output, value = TRUE) expected_total_rows <- n_scenarios * nrow(mat_ref) expect_equal( length(method_label_lines), expected_total_rows, info = paste( "Number of printed method label lines (", length(method_label_lines), ") does not match expected total rows (", expected_total_rows, ")" ) ) }) # =================================================================== # Tests for getAverageNSubjects ------------------------------------- # =================================================================== scenarios_list <- simulateScenarios( n_subjects_list = list(c(10, 20, 30), c(15, 25, 35)), response_rates_list = list(c(0.1, 0.2, 0.3), c(0.15, 0.25, 0.35)), n_trials = 2 ) # ------------------------------------------------------------------- # Test: getAverageNSubjects – structure for multiple scenarios # Input: # - scenario_list with two scenarios. # Behaviour: # - Returns list of numeric vectors, one per scenario. # Expectations: # - Result is a list with length equal to number of scenarios. # - Each element is numeric. # Why: # - Basic contract of getAverageNSubjects. # ------------------------------------------------------------------- test_that("returns correct structure for multiple scenarios", { result <- getAverageNSubjects(scenarios_list) expect_type(result, "list") expect_equal(length(result), length(scenarios_list)) expect_true(all(sapply(result, is.numeric))) }) # ------------------------------------------------------------------- # Test: getAverageNSubjects – computes column means of n_subjects # Input: # - scenarios_list with known n_subjects for scenario_1. # Behaviour: # - For each scenario, colMeans() of n_subjects is returned. # Expectations: # - result[[1]] equals colMeans(scenarios_list[[1]]$n_subjects). # Why: # - Verifies the actual calculation performed. # ------------------------------------------------------------------- test_that("computes correct column means", { expected <- colMeans(scenarios_list[[1]]$n_subjects) result <- getAverageNSubjects(scenarios_list) expect_equal(result[[1]], expected) }) # ------------------------------------------------------------------- # Test: getAverageNSubjects – works for single scenario # Input: # - single_scenario scenario_list with one element. # Behaviour: # - Returns list of length 1, named as input, with column means. # Expectations: # - names(result) match names(single_scenario). # - result[[1]] equals colMeans(single_scenario[[1]]$n_subjects). # Why: # - Handles degenerate one-scenario case correctly. # ------------------------------------------------------------------- test_that("works for single scenario", { single_scenario <- simulateScenarios( n_subjects_list = list(c(10, 20, 30)), response_rates_list = list(c(0.1, 0.2, 0.3)), n_trials = 3 ) result <- getAverageNSubjects(single_scenario) expect_equal(names(result), names(single_scenario)) expect_equal(result[[1]], colMeans(single_scenario[[1]]$n_subjects)) }) # ------------------------------------------------------------------- # Test: getAverageNSubjects – invalid input class # Input: # - list() without class "scenario_list". # Behaviour: # - assertClass(scenario_list, "scenario_list") fails. # Expectations: # - Error mentioning 'scenario_list'. # Why: # - Prevents accidental misuse with arbitrary lists. # ------------------------------------------------------------------- test_that("throws error for invalid input class", { expect_error(getAverageNSubjects(list()), "scenario_list") }) # ------------------------------------------------------------------- # Test: getAverageNSubjects – empty scenario_list # Input: # - empty list with class "scenario_list". # Behaviour: # - lapply over empty list produces empty list. # Expectations: # - result is list(). # Why: # - Defines behaviour for degenerate empty input gracefully. # ------------------------------------------------------------------- test_that("returns empty list for empty scenario_list", { empty_list <- structure(list(), class = "scenario_list") result <- getAverageNSubjects(empty_list) expect_equal(result, list()) }) # =================================================================== # Tests for negateGoDecisions --------------------------------------- # =================================================================== set.seed(101) scenarios_neg <- simulateScenarios( n_subjects_list = list(c(10, 15)), response_rates_list = list(c(0.4, 0.7)), n_trials = 6 ) analyses_neg <- performAnalyses( scenario_list = scenarios_neg, target_rates = c(0.4, 0.4), n_mcmc_iterations = 80 ) coh_neg <- c("p_1", "p_2") go_decisions_list_neg <- getGoDecisions( analyses_list = analyses_neg, cohort_names = coh_neg, evidence_levels = c(0.5, 0.5), boundary_rules = quote(c(TRUE, TRUE)) ) n_scen_neg <- length(go_decisions_list_neg) n_meth_neg <- length(go_decisions_list_neg[[1]]$decisions_list) # ------------------------------------------------------------------- # Test: negateGoDecisions – go_decisions_list must be decision_list # Input: # - go_decisions_list = list(). # Behaviour: # - assertClass(go_decisions_list, "decision_list") fails. # Expectations: # - Error mentioning 'go_decisions_list'. # Why: # - Ensures correct object type as input. # ------------------------------------------------------------------- test_that("negateGoDecisions: go_decisions_list must be a decision_list", { expect_error( negateGoDecisions(go_decisions_list = list()), "go_decisions_list" ) }) # ------------------------------------------------------------------- # Test: negateGoDecisions – overall_min_nogos validation # Input: # - overall_min_nogos = -1L, and overall_min_nogos = "foo". # Behaviour: # - Combined check: either 'all' or non-negative integer. # Expectations: # - Both invalid values trigger error mentioning 'overall_min_nogos'. # Why: # - Ensures threshold is meaningful or 'all'. # ------------------------------------------------------------------- test_that("negateGoDecisions: overall_min_nogos must be 'all' or non-negative integer", { expect_error( negateGoDecisions( go_decisions_list = go_decisions_list_neg, overall_min_nogos = -1L ), "overall_min_nogos" ) expect_error( negateGoDecisions( go_decisions_list = go_decisions_list_neg, overall_min_nogos = "foo" ), "overall_min_nogos" ) }) # ------------------------------------------------------------------- # Test: negateGoDecisions – cohort-level entries are logically negated # Input: # - go_decisions_list_neg with multiple scenarios/methods. # Behaviour: # - All non-overall columns are negated (TRUE->FALSE, FALSE->TRUE). # Expectations: # - For each scenario/method, nogo_coh == !go_coh. # Why: # - Core semantics of “negating” a decision_list. # ------------------------------------------------------------------- test_that("negateGoDecisions: cohort-level entries are logically negated", { nogo_list <- negateGoDecisions( go_decisions_list = go_decisions_list_neg, overall_min_nogos = "all" ) for (s in seq_len(n_scen_neg)) { for (m in seq_len(n_meth_neg)) { go_mat <- go_decisions_list_neg[[s]]$decisions_list[[m]] nogo_mat <- nogo_list[[s]]$decisions_list[[m]] if (ncol(go_mat) > 1L) { go_coh <- go_mat[, -1, drop = FALSE] nogo_coh <- nogo_mat[, -1, drop = FALSE] expect_identical( nogo_coh, !go_coh, info = paste("Cohort decisions not negated in scenario", s, "method", m) ) } } } }) # ------------------------------------------------------------------- # Test: negateGoDecisions – overall_min_nogos = "all" # Input: # - overall_min_nogos = "all". # Behaviour: # - Threshold internally becomes n_decisions - 1 (all cohort columns). # - overall TRUE iff all cohort NoGo columns are TRUE. # Expectations: # - overall column equals row-wise indicator of “all cohorts NoGo”. # Why: # - Confirms the semantics of 'all' for overall NoGo decision. # ------------------------------------------------------------------- test_that("negateGoDecisions: overall_min_nogos = 'all' means 'all cohorts NoGo'", { nogo_list <- negateGoDecisions( go_decisions_list = go_decisions_list_neg, overall_min_nogos = "all" ) for (s in seq_len(n_scen_neg)) { for (m in seq_len(n_meth_neg)) { nogo_mat <- nogo_list[[s]]$decisions_list[[m]] if (ncol(nogo_mat) <= 1L) next n_decisions <- ncol(nogo_mat) nogo_coh <- nogo_mat[, -1, drop = FALSE] expected_over <- apply( nogo_coh, 1, function(x) sum(x) >= (n_decisions - 1L) ) actual_over <- as.logical(nogo_mat[, 1]) expect_identical( actual_over, expected_over ) } } }) # ------------------------------------------------------------------- # Test: negateGoDecisions – overall_min_nogos = 0 # Input: # - overall_min_nogos = 0L. # Behaviour: # - sum(x) >= 0 is always TRUE, so overall should be TRUE in all rows. # Expectations: # - overall column is entirely TRUE for all scenarios/methods. # Why: # - Edge-case semantics of minimal threshold. # ------------------------------------------------------------------- test_that("negateGoDecisions: overall_min_nogos = 0 makes overall always TRUE", { nogo_list <- negateGoDecisions( go_decisions_list = go_decisions_list_neg, overall_min_nogos = 0L ) for (s in seq_len(n_scen_neg)) { for (m in seq_len(n_meth_neg)) { nogo_mat <- nogo_list[[s]]$decisions_list[[m]] if (ncol(nogo_mat) <= 1L) next overall_col <- as.logical(nogo_mat[, 1]) expect_true( all(overall_col) ) } } }) # ------------------------------------------------------------------- # Test: negateGoDecisions – numeric overall_min_nogos = k # Input: # - overall_min_nogos = 1L. # Behaviour: # - overall TRUE iff at least k cohort NoGo decisions are TRUE. # Expectations: # - overall column equals row-wise indicator of sum(no-go) >= k. # Why: # - Confirms general threshold logic for k ≥ 1. # ------------------------------------------------------------------- test_that("negateGoDecisions: numeric overall_min_nogos = k means 'at least k cohorts NoGo'", { k <- 1L nogo_list <- negateGoDecisions( go_decisions_list = go_decisions_list_neg, overall_min_nogos = k ) for (s in seq_len(n_scen_neg)) { for (m in seq_len(n_meth_neg)) { nogo_mat <- nogo_list[[s]]$decisions_list[[m]] if (ncol(nogo_mat) <= 1L) next nogo_coh <- nogo_mat[, -1, drop = FALSE] expected_over <- apply(nogo_coh, 1, function(x) sum(x) >= k) actual_over <- as.logical(nogo_mat[, 1]) expect_identical( actual_over, expected_over, info = paste("Overall NoGo != '>= k cohorts' for k =", k, "in scenario", s, "method", m) ) } } }) # ------------------------------------------------------------------- # Test: negateGoDecisions – single-column decision matrices # Input: # - decision_list with only one column and no explicit overall logic. # Behaviour: # - All entries are simply negated; no overall recomputation. # Expectations: # - new_mat == !orig_mat. # Why: # - Confirms graceful behaviour when there is no explicit overall column. # ------------------------------------------------------------------- test_that("negateGoDecisions: single-column decisions are just negated, no overall logic", { simple_dl <- list( scenario_1 = list( decisions_list = list( method_1 = matrix(c(TRUE, FALSE, TRUE), ncol = 1) ) ) ) class(simple_dl) <- "decision_list" nogo_simple <- negateGoDecisions( go_decisions_list = simple_dl, overall_min_nogos = "all" ) orig_mat <- simple_dl$scenario_1$decisions_list$method_1 new_mat <- nogo_simple$scenario_1$decisions_list$method_1 expect_identical( new_mat, !orig_mat, info = "Single-column decision matrix not fully negated" ) })