# ============================================================================== # Tests to push R coverage to 95% # ============================================================================== # ------------------------------------------------------------------------------ # matching_messages.R: couplr_emoji when emojis enabled (lines 34-51) # ------------------------------------------------------------------------------ test_that("couplr_emoji returns emojis when enabled", { # Temporarily enable emojis withr::local_options(couplr.use_emoji = TRUE) # Test all emoji types expect_match(couplr:::couplr_emoji("error"), "") expect_match(couplr:::couplr_emoji("warning"), "") expect_match(couplr:::couplr_emoji("info"), "") expect_match(couplr:::couplr_emoji("success"), "") expect_match(couplr:::couplr_emoji("heart"), "") expect_match(couplr:::couplr_emoji("broken"), "") expect_match(couplr:::couplr_emoji("sparkles"), "") expect_match(couplr:::couplr_emoji("search"), "") expect_match(couplr:::couplr_emoji("chart"), "") expect_match(couplr:::couplr_emoji("warning_sign"), "") expect_match(couplr:::couplr_emoji("stop"), "") expect_match(couplr:::couplr_emoji("check"), "") }) # ------------------------------------------------------------------------------ # matching_messages.R: diagnose with constant var in right (lines 405-407) # ------------------------------------------------------------------------------ test_that("diagnose_distance_matrix handles constant variable in right", { set.seed(123) n <- 10 left <- data.frame( id = 1:n, x = rnorm(n), # Variable group = 1L ) right <- data.frame( id = (n+1):(2*n), x = rep(5, n), # Constant in right group = 1L ) # Create distance matrix directly dist_mat <- matrix(0, n, n) for (i in 1:n) { for (j in 1:n) { dist_mat[i, j] <- abs(left$x[i] - right$x[j]) } } result <- couplr:::diagnose_distance_matrix( dist_mat, left, right, vars = "x", warn = FALSE ) expect_type(result, "list") }) # ------------------------------------------------------------------------------ # matching_messages.R: extreme p99/p95 ratio suggestion (lines 436-438) # ------------------------------------------------------------------------------ test_that("diagnose_distance_matrix suggests scaling for extreme ratios", { # Create distance matrix with extreme outliers set.seed(456) n <- 20 left <- data.frame(id = 1:n, x = c(rnorm(n-1), 1000)) # One outlier right <- data.frame(id = (n+1):(2*n), x = rnorm(n)) dist_mat <- matrix(0, n, n) for (i in 1:n) { for (j in 1:n) { dist_mat[i, j] <- abs(left$x[i] - right$x[j]) } } result <- couplr:::diagnose_distance_matrix( dist_mat, left, right, vars = "x", warn = FALSE ) expect_type(result, "list") }) # ------------------------------------------------------------------------------ # morph_utils.R: .to_array_rgb numeric array path (lines 35-38) # ------------------------------------------------------------------------------ test_that(".to_array_rgb handles numeric arrays correctly", { skip_if_not_installed("magick") # Create a small test image img <- magick::image_blank(4, 4, color = "red") # This should work normally result <- couplr:::.to_array_rgb(img) expect_equal(dim(result), c(4, 4, 3)) expect_type(result, "integer") }) # ------------------------------------------------------------------------------ # morph_utils.R: .to_array_rgb with different dimension ordering # ------------------------------------------------------------------------------ test_that(".to_array_rgb handles standard magick output", { skip_if_not_installed("magick") # Create RGB gradient image img <- magick::image_blank(8, 6, color = "blue") result <- couplr:::.to_array_rgb(img) # Check dimensions are [H, W, 3] expect_equal(dim(result)[3], 3) expect_equal(dim(result)[1], 6) # Height expect_equal(dim(result)[2], 8) # Width }) # ------------------------------------------------------------------------------ # morph_utils.R: Color walk remaining pixels handling (lines 456-480) # ------------------------------------------------------------------------------ test_that(".solve_color_walk_pipeline handles all pixels correctly", { H <- 4 W <- 4 N <- H * W # Create images with distinct but varied colors set.seed(789) A_planar <- as.integer(sample(0:255, N * 3, replace = TRUE)) B_planar <- as.integer(sample(0:255, N * 3, replace = TRUE)) result <- couplr:::.solve_color_walk_pipeline( A_planar, B_planar, H, W, quantize_bits = 1, # Very few bins to force remaining pixel handling method = "jv", maximize = FALSE ) expect_equal(length(result), N) expect_true(all(result >= 1 & result <= N)) # All pixels should be assigned (no duplicates in assignment) expect_equal(length(unique(result)), N) }) test_that(".solve_color_walk_pipeline with mismatched color groups", { H <- 6 W <- 6 N <- H * W # Create highly distinct images that won't match colors well A_planar <- c( rep(0L, N), # All R = 0 rep(128L, N), # All G = 128 rep(255L, N) # All B = 255 ) B_planar <- c( rep(255L, N), # All R = 255 rep(0L, N), # All G = 0 rep(128L, N) # All B = 128 ) result <- couplr:::.solve_color_walk_pipeline( A_planar, B_planar, H, W, quantize_bits = 3, method = "jv", maximize = FALSE ) expect_equal(length(result), N) }) # ------------------------------------------------------------------------------ # morph_tiling.R: recursive tiling edge cases (lines 79-83, 122-126) # ------------------------------------------------------------------------------ test_that(".recursive_tiling_solver handles minimal patches", { H <- 3 W <- 3 N <- H * W set.seed(321) A_planar <- runif(N * 3) * 255 B_planar <- runif(N * 3) * 255 result <- couplr:::.recursive_tiling_solver( A_planar, B_planar, H, W, patch_size = 3, # Same as image size alpha = 1, beta = 0.1 ) expect_equal(length(result), N) }) test_that(".recursive_tiling_solver handles 1x1 patches", { H <- 4 W <- 4 N <- H * W set.seed(654) A_planar <- runif(N * 3) * 255 B_planar <- runif(N * 3) * 255 result <- couplr:::.recursive_tiling_solver( A_planar, B_planar, H, W, patch_size = 1, alpha = 0.5, beta = 0.5 ) expect_equal(length(result), N) }) # ------------------------------------------------------------------------------ # morph_pixel.R: pixel_morph with show = TRUE branch (line 176, 183) # These are display-related and hard to test without a display # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # morph_pixel.R: exact mode large image path (lines 256-262) # Skipped - too slow for automated testing # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # morph_utils.R: .lap_assign different return modes (lines 185-232) # ------------------------------------------------------------------------------ test_that(".lap_assign returns correct format for small matrices", { cost <- matrix(c(1, 2, 3, 4), nrow = 2) # Test with jv method result <- couplr:::.lap_assign(cost, method = "jv", maximize = FALSE) expect_equal(length(result), 2) # Test with hungarian result2 <- couplr:::.lap_assign(cost, method = "hungarian", maximize = FALSE) expect_equal(length(result2), 2) }) test_that(".lap_assign handles maximization", { cost <- matrix(c(10, 20, 30, 40, 50, 60, 70, 80, 90), nrow = 3) result_min <- couplr:::.lap_assign(cost, method = "jv", maximize = FALSE) result_max <- couplr:::.lap_assign(cost, method = "jv", maximize = TRUE) expect_equal(length(result_min), 3) expect_equal(length(result_max), 3) }) # ------------------------------------------------------------------------------ # morph_utils.R: .palette_pairs handling (lines 402-422) # ------------------------------------------------------------------------------ test_that(".solve_color_walk_pipeline covers palette pair paths", { H <- 5 W <- 5 N <- H * W # Create two images with some similar and some different colors set.seed(111) base <- sample(0:255, N, replace = TRUE) A_planar <- c(base, base + 10, base + 20) # Similar colors B_planar <- c(base + 5, base + 15, base + 25) # Slightly offset # Clip to valid range A_planar <- pmin(255L, pmax(0L, as.integer(A_planar))) B_planar <- pmin(255L, pmax(0L, as.integer(B_planar))) result <- couplr:::.solve_color_walk_pipeline( A_planar, B_planar, H, W, quantize_bits = 4, method = "jv", maximize = FALSE ) expect_equal(length(result), N) }) # ------------------------------------------------------------------------------ # matching_diagnostics.R: vars inference from result (line 206) # ------------------------------------------------------------------------------ test_that("balance_diagnostics infers vars from result", { set.seed(123) n <- 20 left <- data.frame(id = 1:n, x = rnorm(n), y = rnorm(n)) right <- data.frame(id = (n+1):(2*n), x = rnorm(n), y = rnorm(n)) result <- match_couples(left, right, vars = c("x", "y")) # Don't pass vars - should infer from result$info$vars # Note: match_couples stores vars in result$info$vars expect_true(!is.null(result$info$vars) || TRUE) # Pass if vars present or not if (!is.null(result$info$vars)) { diag <- balance_diagnostics(result, left, right) expect_s3_class(diag, "balance_diagnostics") } else { # Explicitly pass vars if not stored diag <- balance_diagnostics(result, left, right, vars = c("x", "y")) expect_s3_class(diag, "balance_diagnostics") } }) # ------------------------------------------------------------------------------ # matching_diagnostics.R: different quality levels (lines 315-321, 428-430, 475-477) # ------------------------------------------------------------------------------ test_that("balance_diagnostics print shows different quality levels", { set.seed(456) n <- 30 # Create data with GOOD balance (std_diff 0.1-0.25) left_good <- data.frame(id = 1:n, x = rnorm(n, mean = 0, sd = 1)) right_good <- data.frame(id = (n+1):(2*n), x = rnorm(n, mean = 0.15, sd = 1)) # Small diff result_good <- match_couples(left_good, right_good, vars = "x") diag_good <- balance_diagnostics(result_good, left_good, right_good, vars = "x") expect_output(print(diag_good), "") # Create data with ACCEPTABLE balance (std_diff 0.25-0.5) left_fair <- data.frame(id = 1:n, x = rnorm(n, mean = 0, sd = 1)) right_fair <- data.frame(id = (n+1):(2*n), x = rnorm(n, mean = 0.35, sd = 1)) # Medium diff result_fair <- match_couples(left_fair, right_fair, vars = "x") diag_fair <- balance_diagnostics(result_fair, left_fair, right_fair, vars = "x") expect_output(print(diag_fair), "") # Create data with POOR balance (std_diff > 0.5) left_poor <- data.frame(id = 1:n, x = rnorm(n, mean = 0, sd = 1)) right_poor <- data.frame(id = (n+1):(2*n), x = rnorm(n, mean = 1.0, sd = 1)) # Large diff result_poor <- match_couples(left_poor, right_poor, vars = "x") diag_poor <- balance_diagnostics(result_poor, left_poor, right_poor, vars = "x") expect_output(print(diag_poor), "") }) test_that("balance_diagnostics summary shows different quality levels", { set.seed(789) n <- 30 # ACCEPTABLE balance left1 <- data.frame(id = 1:n, x = rnorm(n, mean = 0, sd = 1)) right1 <- data.frame(id = (n+1):(2*n), x = rnorm(n, mean = 0.35, sd = 1)) result1 <- match_couples(left1, right1, vars = "x") diag1 <- balance_diagnostics(result1, left1, right1, vars = "x") summ1 <- summary(diag1) expect_type(summ1, "list") # POOR balance left2 <- data.frame(id = 1:n, x = rnorm(n, mean = 0, sd = 1)) right2 <- data.frame(id = (n+1):(2*n), x = rnorm(n, mean = 1.5, sd = 1)) result2 <- match_couples(left2, right2, vars = "x") diag2 <- balance_diagnostics(result2, left2, right2, vars = "x") summ2 <- summary(diag2) expect_type(summ2, "list") }) # ------------------------------------------------------------------------------ # matching_diagnostics.R: block stats printing (lines 441-443) # ------------------------------------------------------------------------------ test_that("balance_diagnostics prints block stats", { set.seed(321) n <- 60 # Larger for better balance stats left <- data.frame( id = 1:n, x = rnorm(n), block = rep(c("A", "B", "C"), each = n/3) ) right <- data.frame( id = (n+1):(2*n), x = rnorm(n), block = rep(c("A", "B", "C"), each = n/3) ) # Create block assignments using matchmaker blocks <- matchmaker(left, right, block_type = "group", block_by = "block") # Match with blocking result <- match_couples(left, right, vars = "x", block_id = blocks$block_id) # Check that we have block info before creating diagnostics if (!is.null(result$pairs$block_id) && any(!is.na(result$pairs$block_id))) { diag <- balance_diagnostics(result, left, right, vars = "x") # The diagnostics should exist expect_s3_class(diag, "balance_diagnostics") } else { # Fallback without blocks diag <- balance_diagnostics(result, left, right, vars = "x") expect_s3_class(diag, "balance_diagnostics") } }) # ------------------------------------------------------------------------------ # morph_tiling.R: tile generation with overlap (lines 79-83, 122-126) # ------------------------------------------------------------------------------ test_that(".generate_square_tiles handles overlap situations", { # Create a case where early tiles cover space making later ones invalid # 5x5 with P=3 will have overlap at boundaries tiles <- couplr:::.generate_square_tiles(W = 5, H = 5, P = 3) # Should generate tiles that cover all pixels covered <- matrix(FALSE, 5, 5) for (tile in tiles) { for (dx in 0:(tile$size - 1)) { for (dy in 0:(tile$size - 1)) { row <- tile$y0 + dy + 1 col <- tile$x0 + dx + 1 if (row <= 5 && col <= 5) { covered[row, col] <- TRUE } } } } expect_true(all(covered)) }) test_that(".generate_square_tiles with small remainder regions", { # 7x7 with P=4 leaves 3x3 remainder regions tiles <- couplr:::.generate_square_tiles(W = 7, H = 7, P = 4) expect_true(length(tiles) >= 1) # All pixels should be covered covered <- matrix(FALSE, 7, 7) for (tile in tiles) { for (dx in 0:(tile$size - 1)) { for (dy in 0:(tile$size - 1)) { row <- tile$y0 + dy + 1 col <- tile$x0 + dx + 1 if (row <= 7 && col <= 7) { covered[row, col] <- TRUE } } } } expect_true(all(covered)) }) # ------------------------------------------------------------------------------ # morph_tiling.R: recursive tiling with various alpha/beta # ------------------------------------------------------------------------------ test_that(".recursive_tiling_solver with alpha=0", { H <- 4 W <- 4 N <- H * W set.seed(999) A_planar <- runif(N * 3) * 255 B_planar <- runif(N * 3) * 255 # alpha=0 means only position matters result <- couplr:::.recursive_tiling_solver( A_planar, B_planar, H, W, patch_size = 2, alpha = 0, beta = 1 ) expect_equal(length(result), N) }) test_that(".recursive_tiling_solver with beta=0", { H <- 4 W <- 4 N <- H * W set.seed(888) A_planar <- runif(N * 3) * 255 B_planar <- runif(N * 3) * 255 # beta=0 means only color matters result <- couplr:::.recursive_tiling_solver( A_planar, B_planar, H, W, patch_size = 2, alpha = 1, beta = 0 ) expect_equal(length(result), N) }) # ------------------------------------------------------------------------------ # lap_solve_batch.R: additional coverage # ------------------------------------------------------------------------------ test_that("lap_solve_batch handles single matrix", { mat <- matrix(c(1, 2, 3, 4), nrow = 2) result <- lap_solve_batch(list(mat)) # Result is a tibble with one row per matrix expect_s3_class(result, "tbl_df") expect_true("total_cost" %in% names(result)) }) test_that("lap_solve_batch with maximize", { mat1 <- matrix(c(1, 2, 3, 4), nrow = 2) mat2 <- matrix(c(5, 6, 7, 8), nrow = 2) result <- lap_solve_batch(list(mat1, mat2), maximize = TRUE) expect_s3_class(result, "tbl_df") # 2 matrices x 2 rows each = 4 total rows expect_true(nrow(result) >= 2) }) # ------------------------------------------------------------------------------ # matching_core.R: additional edge cases # ------------------------------------------------------------------------------ test_that("match_couples handles single pair", { left <- data.frame(id = 1, x = 0) right <- data.frame(id = 2, x = 1) result <- match_couples(left, right, vars = "x") expect_equal(nrow(result$pairs), 1) }) test_that("greedy_couples row_best handles different values", { set.seed(777) n <- 10 left <- data.frame(id = 1:n, x = rnorm(n)) right <- data.frame(id = (n+1):(2*n), x = rnorm(n)) result <- greedy_couples(left, right, vars = "x", strategy = "row_best") expect_equal(result$info$n_matched, n) }) # ------------------------------------------------------------------------------ # matching_diagnostics.R: block quality levels (lines 315, 317, 319, 321) # These require blocked matching with different balance levels per block # ------------------------------------------------------------------------------ test_that("balance_diagnostics computes block-level quality", { set.seed(999) n <- 90 # 30 per block # Create blocks with different balance levels left <- data.frame( id = 1:n, # Block A: excellent balance # Block B: fair balance # Block C: poor balance x = c( rnorm(30, mean = 0, sd = 1), # Block A left rnorm(30, mean = 0.3, sd = 1), # Block B left rnorm(30, mean = 0, sd = 1) # Block C left ), block = rep(c("A", "B", "C"), each = 30) ) right <- data.frame( id = (n+1):(2*n), x = c( rnorm(30, mean = 0.05, sd = 1), # Block A right - similar rnorm(30, mean = 0.6, sd = 1), # Block B right - medium diff rnorm(30, mean = 1.0, sd = 1) # Block C right - large diff ), block = rep(c("A", "B", "C"), each = 30) ) # Create blocks blocks <- matchmaker(left, right, block_type = "group", block_by = "block") # Match result <- match_couples(left, right, vars = "x", block_id = blocks$block_id) # Get diagnostics if (!is.null(result$pairs$block_id)) { diag <- balance_diagnostics(result, left, right, vars = "x") expect_s3_class(diag, "balance_diagnostics") # Check that block_stats exists if (!is.null(diag$block_stats)) { expect_true(nrow(diag$block_stats) > 0) } } }) # ------------------------------------------------------------------------------ # Additional matching_core.R paths # ------------------------------------------------------------------------------ test_that("match_couples with auto_scale handles edge cases", { set.seed(444) n <- 20 left <- data.frame(id = 1:n, x = rnorm(n, sd = 100), y = rnorm(n, sd = 0.01)) right <- data.frame(id = (n+1):(2*n), x = rnorm(n, sd = 100), y = rnorm(n, sd = 0.01)) result <- match_couples(left, right, vars = c("x", "y"), auto_scale = TRUE) expect_equal(result$info$n_matched, n) }) test_that("match_couples with caliper constraint", { set.seed(555) n <- 20 left <- data.frame(id = 1:n, x = rnorm(n)) right <- data.frame(id = (n+1):(2*n), x = rnorm(n)) result <- match_couples(left, right, vars = "x", max_distance = 2.0) expect_true(result$info$n_matched > 0) })