# Tests for R/rmst-interface.R # Covers: .parse_rmst_formula, .resolve_strata, .rmst_route, .rmst_model_label, # rmst.power, rmst.ss, and all S3 methods (print/summary/plot). # ── Shared pilot data helpers ───────────────────────────────────────────────── .iface_pilot <- function(n = 50L, with_strata = FALSE) { set.seed(8888L) dat <- data.frame( time = stats::rexp(n, rate = 0.15) + 0.1, status = stats::rbinom(n, 1L, 0.75), arm = rep(0L:1L, length.out = n), age = stats::rnorm(n, 60, 8), stringsAsFactors = FALSE ) if (with_strata) dat$region <- factor(rep(c("A", "B"), length.out = n)) dat } .dc_pilot <- function(n = 200L, seed = 42L) { set.seed(seed) dat <- data.frame( arm = rep(0L:1L, each = n %/% 2L), age = stats::rnorm(n, 60, 8) ) dat$time <- stats::rexp(n, rate = ifelse(dat$arm == 0L, 0.10, 0.08)) ev <- sample(0:2, n, replace = TRUE, prob = c(0.6, 0.2, 0.2)) dat$status <- as.integer(ev == 0L) dat$comp_event <- as.integer(ev == 1L) dat } pilot <- .iface_pilot(50L) pilot_s <- .iface_pilot(200L, with_strata = TRUE) dc_dat <- .dc_pilot(200L) # ── .parse_rmst_formula ─────────────────────────────────────────────────────── test_that(".parse_rmst_formula parses Surv(t, s) ~ x correctly", { p <- RMSTpowerBoost:::.parse_rmst_formula( survival::Surv(time, status) ~ age ) expect_identical(p$time_var, "time") expect_identical(p$status_var, "status") expect_identical(p$linear_terms, "age") expect_null(p$smooth_terms) }) test_that(".parse_rmst_formula detects s() smooth terms", { p <- RMSTpowerBoost:::.parse_rmst_formula( survival::Surv(time, status) ~ s(age) ) expect_null(p$linear_terms) expect_identical(p$smooth_terms, "age") }) test_that(".parse_rmst_formula handles mixed smooth + linear terms", { p <- RMSTpowerBoost:::.parse_rmst_formula( survival::Surv(time, status) ~ age + s(bmi) ) expect_identical(p$linear_terms, "age") expect_identical(p$smooth_terms, "bmi") }) test_that(".parse_rmst_formula handles intercept-only RHS (no covariates)", { p <- RMSTpowerBoost:::.parse_rmst_formula( survival::Surv(time, status) ~ 1 ) expect_null(p$linear_terms) expect_null(p$smooth_terms) }) test_that(".parse_rmst_formula errors when LHS is not Surv()", { expect_error( RMSTpowerBoost:::.parse_rmst_formula(time ~ age), "Surv" ) }) # ── .resolve_strata ─────────────────────────────────────────────────────────── test_that(".resolve_strata returns NULL for NULL input", { expect_null(RMSTpowerBoost:::.resolve_strata(NULL)) }) test_that(".resolve_strata returns character string unchanged", { expect_identical(RMSTpowerBoost:::.resolve_strata("region"), "region") }) test_that(".resolve_strata extracts column name from one-sided formula", { expect_identical(RMSTpowerBoost:::.resolve_strata(~region), "region") }) test_that(".resolve_strata errors when formula has two terms", { expect_error( RMSTpowerBoost:::.resolve_strata(~a + b), "exactly one column" ) }) test_that(".resolve_strata errors on unsupported input type", { expect_error(RMSTpowerBoost:::.resolve_strata(42)) expect_error(RMSTpowerBoost:::.resolve_strata(list("x"))) }) # ── .rmst_route ─────────────────────────────────────────────────────────────── test_that(".rmst_route dep_cens+boot auto-switches to analytical with message", { expect_message( r <- RMSTpowerBoost:::.rmst_route(TRUE, FALSE, NULL, "additive", "boot"), "switching type" ) expect_identical(r$model, "DC") expect_identical(r$method_used, "analytical") expect_identical(r$fn_power, RMSTpowerBoost::DC.power.analytical) expect_identical(r$fn_ss, RMSTpowerBoost::DC.ss.analytical) }) test_that(".rmst_route dep_cens+analytical returns DC analytical silently", { expect_silent( r <- RMSTpowerBoost:::.rmst_route(TRUE, FALSE, NULL, "additive", "analytical") ) expect_identical(r$model, "DC") expect_identical(r$method_used, "analytical") }) test_that(".rmst_route has_smooth+analytical auto-switches to boot with message", { expect_message( r <- RMSTpowerBoost:::.rmst_route(FALSE, TRUE, NULL, "additive", "analytical"), "switching type" ) expect_identical(r$model, "GAM") expect_identical(r$method_used, "boot") }) test_that(".rmst_route has_smooth+boot returns GAM boot silently", { expect_silent( r <- RMSTpowerBoost:::.rmst_route(FALSE, TRUE, "region", "additive", "boot") ) expect_identical(r$model, "GAM") expect_identical(r$fn_power, RMSTpowerBoost::GAM.power.boot) }) test_that(".rmst_route no-strata+analytical returns linear analytical", { r <- RMSTpowerBoost:::.rmst_route(FALSE, FALSE, NULL, "additive", "analytical") expect_identical(r$model, "linear") expect_identical(r$fn_power, RMSTpowerBoost::linear.power.analytical) expect_identical(r$fn_ss, RMSTpowerBoost::linear.ss.analytical) }) test_that(".rmst_route no-strata+boot returns linear boot", { r <- RMSTpowerBoost:::.rmst_route(FALSE, FALSE, NULL, "additive", "boot") expect_identical(r$model, "linear") expect_identical(r$fn_power, RMSTpowerBoost::linear.power.boot) expect_identical(r$fn_ss, RMSTpowerBoost::linear.ss.boot) }) test_that(".rmst_route strata+additive+analytical returns additive analytical", { r <- RMSTpowerBoost:::.rmst_route(FALSE, FALSE, "region", "additive", "analytical") expect_identical(r$model, "additive") expect_identical(r$fn_power, RMSTpowerBoost::additive.power.analytical) expect_identical(r$fn_ss, RMSTpowerBoost::additive.ss.analytical) }) test_that(".rmst_route strata+additive+boot returns GAM boot", { r <- RMSTpowerBoost:::.rmst_route(FALSE, FALSE, "region", "additive", "boot") expect_identical(r$model, "GAM") expect_identical(r$fn_power, RMSTpowerBoost::GAM.power.boot) }) test_that(".rmst_route strata+multiplicative+analytical returns MS analytical", { r <- RMSTpowerBoost:::.rmst_route(FALSE, FALSE, "region", "multiplicative", "analytical") expect_identical(r$model, "multiplicative") expect_identical(r$fn_power, RMSTpowerBoost::MS.power.analytical) expect_identical(r$fn_ss, RMSTpowerBoost::MS.ss.analytical) }) test_that(".rmst_route strata+multiplicative+boot returns MS boot", { r <- RMSTpowerBoost:::.rmst_route(FALSE, FALSE, "region", "multiplicative", "boot") expect_identical(r$model, "multiplicative") expect_identical(r$fn_power, RMSTpowerBoost::MS.power.boot) expect_identical(r$fn_ss, RMSTpowerBoost::MS.ss.boot) }) # ── .rmst_model_label ───────────────────────────────────────────────────────── test_that(".rmst_model_label produces correct labels for all model/method pairs", { mk <- function(m, mt) list(model = m, method = mt) expect_true(grepl("Linear IPCW", RMSTpowerBoost:::.rmst_model_label(mk("linear", "analytical")))) expect_true(grepl("Analytical", RMSTpowerBoost:::.rmst_model_label(mk("linear", "analytical")))) expect_true(grepl("Bootstrap", RMSTpowerBoost:::.rmst_model_label(mk("linear", "boot")))) expect_true(grepl("Additive Stratified", RMSTpowerBoost:::.rmst_model_label(mk("additive", "boot")))) expect_true(grepl("Multiplicative", RMSTpowerBoost:::.rmst_model_label(mk("multiplicative", "analytical")))) expect_true(grepl("GAM", RMSTpowerBoost:::.rmst_model_label(mk("GAM", "boot")))) expect_true(grepl("Dependent Censoring", RMSTpowerBoost:::.rmst_model_label(mk("DC", "analytical")))) expect_true(grepl("my_model", RMSTpowerBoost:::.rmst_model_label(mk("my_model", "boot")))) }) # ── rmst.power ──────────────────────────────────────────────────────────────── test_that("rmst.power linear analytical returns rmst_power with 5-element structure", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", sample_sizes = c(30L, 50L), L = 5) expect_s3_class(r, "rmst_power") expect_named(r, c("results_data", "results_plot", "results_summary", "model_output", ".meta")) expect_s3_class(r$results_data, "data.frame") expect_identical(r$.meta$model, "linear") expect_identical(r$.meta$method, "analytical") expect_identical(r$.meta$truncation, 5) expect_identical(r$.meta$n_col, "N_per_Arm") }) test_that("rmst.power linear boot uses n_sim and returns correct class", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", sample_sizes = c(20L), L = 5, type = "boot", n_sim = 2L) expect_s3_class(r, "rmst_power") expect_identical(r$.meta$method, "boot") }) test_that("rmst.power strata as formula sets strata metadata", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot_s, arm = "arm", sample_sizes = c(30L), L = 5, strata = ~region, strata_type = "multiplicative") expect_identical(r$.meta$strata, "region") }) test_that("rmst.power strata as character string sets strata metadata", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot_s, arm = "arm", sample_sizes = c(30L), L = 5, strata = "region", strata_type = "multiplicative") expect_identical(r$.meta$strata, "region") }) test_that("rmst.power multiplicative strata sets N_per_Stratum n_col", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot_s, arm = "arm", sample_sizes = c(30L), L = 5, strata = "region", strata_type = "multiplicative") expect_identical(r$.meta$n_col, "N_per_Stratum") }) test_that("rmst.power DC boot->analytical emits switching message", { expect_message( r <- rmst.power(survival::Surv(time, status) ~ age, data = dc_dat, arm = "arm", sample_sizes = c(100L), L = 8, dep_cens = TRUE, type = "boot"), "switching type" ) expect_identical(r$.meta$model, "DC") expect_identical(r$.meta$method, "analytical") expect_identical(r$.meta$type_requested, "boot") }) test_that("rmst.power errors on formula without Surv() LHS", { expect_error( rmst.power(time ~ age, data = pilot, arm = "arm", sample_sizes = c(30L), L = 5), "Surv" ) }) # ── rmst.ss ─────────────────────────────────────────────────────────────────── test_that("rmst.ss linear analytical returns rmst_ss with 5-element structure", { r <- rmst.ss(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", target_power = 0.02, L = 5, n_start = 20L, n_step = 10L, max_n = 60L) expect_s3_class(r, "rmst_ss") expect_named(r, c("results_data", "results_plot", "results_summary", "model_output", ".meta")) expect_identical(r$.meta$model, "linear") expect_identical(r$.meta$method, "analytical") expect_identical(r$.meta$n_col, "N_per_Arm") }) test_that("rmst.ss DC boot->analytical emits switching message", { expect_message( r <- rmst.ss(survival::Surv(time, status) ~ age, data = dc_dat, arm = "arm", target_power = 0.50, L = 8, dep_cens = TRUE, type = "boot", n_start = 50L, n_step = 50L, max_n = 300L), "switching type" ) expect_identical(r$.meta$model, "DC") }) # ── print.rmst_power ────────────────────────────────────────────────────────── test_that("print.rmst_power prints without error and returns x invisibly", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", sample_sizes = c(30L, 50L), L = 5) out <- capture.output(ret <- print(r)) expect_identical(ret, r) expect_true(any(grepl("RMST Power Analysis", out))) expect_true(any(grepl("Truncation time", out))) expect_true(any(grepl("Power Results", out))) }) test_that("print.rmst_power shows strata line when strata is non-NULL", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot_s, arm = "arm", sample_sizes = c(30L), L = 5, strata = "region", strata_type = "multiplicative") out <- capture.output(print(r)) expect_true(any(grepl("Strata", out))) }) test_that("print.rmst_power shows Note when type was auto-switched", { r <- suppressMessages( rmst.power(survival::Surv(time, status) ~ age, data = dc_dat, arm = "arm", sample_sizes = c(100L), L = 8, dep_cens = TRUE, type = "boot") ) out <- capture.output(print(r)) expect_true(any(grepl("Note", out))) }) # ── print.rmst_ss ───────────────────────────────────────────────────────────── test_that("print.rmst_ss prints without error and returns x invisibly", { r <- rmst.ss(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", target_power = 0.02, L = 5, n_start = 20L, n_step = 10L, max_n = 60L) out <- capture.output(ret <- print(r)) expect_identical(ret, r) expect_true(any(grepl("RMST Sample Size", out))) expect_true(any(grepl("Truncation time", out))) }) test_that("print.rmst_ss shows Note when type was auto-switched", { r <- suppressMessages( rmst.ss(survival::Surv(time, status) ~ age, data = dc_dat, arm = "arm", target_power = 0.50, L = 8, dep_cens = TRUE, type = "boot", n_start = 50L, n_step = 50L, max_n = 300L) ) out <- capture.output(print(r)) expect_true(any(grepl("Note", out))) }) # ── summary.rmst_power ──────────────────────────────────────────────────────── test_that("summary.rmst_power returns summary.rmst_power with ten named fields", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", sample_sizes = c(30L), L = 5) s <- summary(r) expect_s3_class(s, "summary.rmst_power") expect_true(all(c("model_info", "power_results", "effect_summary", "coefficient_table", "treatment_effect", "arm_specific_rmst", "variance_components", "censoring_weights", "diagnostics", "simulation_draws") %in% names(s))) expect_s3_class(s$model_info, "data.frame") expect_identical(s$model_info$Model, "linear") }) test_that("summary.rmst_power strata meta shows non-None Strata field", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot_s, arm = "arm", sample_sizes = c(30L), L = 5, strata = "region", strata_type = "multiplicative") s <- summary(r) expect_identical(s$model_info$Strata, "region") }) # ── summary.rmst_ss ─────────────────────────────────────────────────────────── test_that("summary.rmst_ss returns summary.rmst_ss with required fields", { r <- rmst.ss(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", target_power = 0.02, L = 5, n_start = 20L, n_step = 10L, max_n = 60L) s <- summary(r) expect_s3_class(s, "summary.rmst_ss") expect_true(all(c("model_info", "ss_results", "effect_summary", "treatment_effect", "diagnostics") %in% names(s))) }) # ── print.summary.rmst_power ────────────────────────────────────────────────── test_that("print.summary.rmst_power (analytical) covers main sections", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", sample_sizes = c(30L), L = 5) s <- summary(r) out <- capture.output(ret <- print(s)) expect_identical(ret, s) expect_true(any(grepl("Model Information", out))) expect_true(any(grepl("Power Results", out))) }) test_that("print.summary.rmst_power (boot) shows NULL coefficient_table note", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", sample_sizes = c(20L), L = 5, type = "boot", n_sim = 2L) s <- summary(r) out <- capture.output(print(s)) expect_true(any(grepl("not available", out))) }) test_that("print.summary.rmst_power mock covers all optional output sections", { mock <- structure( list( model_info = data.frame( Model = "linear", Method = "analytical", Truncation_time = 10, Alpha = 0.05, Arm_variable = "arm", Strata = "None", Dep_censoring = FALSE, stringsAsFactors = FALSE ), power_results = data.frame(N_per_Arm = c(50L, 100L), Power = c(0.5, 0.8)), effect_summary = data.frame(estimand = "RMST diff", estimate = 1.2), coefficient_table = data.frame(term = "arm", estimate = 1.2, p.value = 0.03), treatment_effect = data.frame(estimand = "RMST diff", estimate = 1.2, std_error = 0.5), arm_specific_rmst = data.frame(arm = c(0L, 1L), rmst = c(8.0, 9.2), std_error = c(NA_real_, NA_real_)), variance_components = list( se_effect_n1 = 0.5, A_hat = matrix(1, 1L, 1L), B_hat = matrix(1, 1L, 1L), V_hat_n = matrix(1, 1L, 1L) ), censoring_weights = list( raw_summary = stats::quantile(c(0.5, 0.8, 1.0), c(0.25, 0.5, 0.75)), cap_value = 2.0, capped_fraction = 0.05 ), diagnostics = list(n_used = 100L, n_events = 75L, n_sim = 10L, convergence_ok = TRUE, singular_flag = FALSE), simulation_draws = data.frame( replicate = 1:3, estimate = c(1.1, 1.2, 1.3), std_error = NA_real_, p_value = c(0.01, 0.02, 0.03) ) ), class = "summary.rmst_power" ) out <- capture.output(ret <- print(mock)) expect_identical(ret, mock) expect_true(any(grepl("Coefficient Table", out))) expect_true(any(grepl("Arm-Specific RMST", out))) expect_true(any(grepl("Variance Components", out))) expect_true(any(grepl("Censoring Weights", out))) expect_true(any(grepl("Simulation Draws", out))) expect_true(any(grepl("Note: SE", out))) # NA std_error note }) # ── print.summary.rmst_ss ───────────────────────────────────────────────────── test_that("print.summary.rmst_ss prints all sections and returns x invisibly", { r <- rmst.ss(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", target_power = 0.02, L = 5, n_start = 20L, n_step = 10L, max_n = 60L) s <- summary(r) out <- capture.output(ret <- print(s)) expect_identical(ret, s) expect_true(any(grepl("Sample Size Result", out))) }) # ── plot.rmst_power / plot.rmst_ss ──────────────────────────────────────────── test_that("plot.rmst_power returns ggplot invisibly without error", { r <- rmst.power(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", sample_sizes = c(30L, 50L), L = 5) ret <- expect_no_error(plot(r)) expect_true(inherits(ret, "ggplot")) }) test_that("plot.rmst_ss returns ggplot invisibly without error", { r <- rmst.ss(survival::Surv(time, status) ~ age, data = pilot, arm = "arm", target_power = 0.02, L = 5, n_start = 20L, n_step = 10L, max_n = 60L) ret <- expect_no_error(plot(r)) expect_true(inherits(ret, "ggplot")) })