# test-coverage-remaining.R # Tests for remaining uncovered lines across multiple source files. # ============================================ # 1. aes-nodes.R: node_svg, svg_preserve_aspect, # donut_border_width, map_node_colors, scale_node_sizes # ============================================ test_that("sn_nodes() handles node_svg parameter with inline SVG", { adj <- create_test_matrix(3) net <- cograph(adj) svg_content <- '' # Should register the SVG shape and set it (lines 199, 201-204, 209) result <- sn_nodes(net, node_svg = svg_content) expect_cograph_network(result) aes <- result$network$get_node_aes() # The shape should be set to a temp SVG name expect_true(grepl("^_temp_svg_", aes$shape[1])) }) test_that("sn_nodes() handles svg_preserve_aspect parameter", { adj <- create_test_matrix(3) net <- cograph(adj) # Line 214 result <- sn_nodes(net, svg_preserve_aspect = FALSE) expect_cograph_network(result) aes <- result$network$get_node_aes() expect_false(aes$svg_preserve_aspect) }) test_that("sn_nodes() handles donut_border_width parameter", { adj <- create_test_matrix(3) net <- cograph(adj) # Line 285 result <- sn_nodes(net, donut_border_width = 2) expect_cograph_network(result) aes <- result$network$get_node_aes() expect_equal(aes$donut_border_width, 2) }) test_that("map_node_colors() works with default palette", { # Lines 409-410, 412-415, 417, 420 map_fn <- cograph:::map_node_colors # Default palette (NULL) groups <- c("A", "B", "A", "C") colors <- map_fn(groups) expect_length(colors, 4) expect_equal(colors[1], colors[3]) # Same group = same color # Function palette colors_fn <- map_fn(groups, palette = grDevices::rainbow) expect_length(colors_fn, 4) # Character vector palette colors_vec <- map_fn(groups, palette = c("red", "blue", "green")) expect_length(colors_vec, 4) expect_equal(colors_vec[1], "red") # First group maps to first color }) test_that("scale_node_sizes() handles edge cases", { # Lines 432, 434, 436-437, 441-442 scale_fn <- cograph:::scale_node_sizes # All NA values (line 432) result_na <- scale_fn(c(NA, NA, NA)) expect_length(result_na, 3) expect_true(all(result_na == mean(c(0.03, 0.1)))) # All same values (lines 436-437) result_same <- scale_fn(c(5, 5, 5)) expect_length(result_same, 3) expect_true(all(result_same == mean(c(0.03, 0.1)))) # Normal scaling (lines 434, 441-442) result_normal <- scale_fn(c(1, 5, 10)) expect_length(result_normal, 3) expect_equal(result_normal[1], 0.03) expect_equal(result_normal[3], 0.1) }) # ============================================ # 2. aes-edges.R: label_bg_padding, label_border_color, # label_underline, scale_edge_widths_simple, map_edge_colors # ============================================ test_that("sn_edges() handles label_bg_padding parameter", { adj <- create_test_matrix(3) net <- cograph(adj) # Line 379 result <- sn_edges(net, label_bg_padding = 0.5) expect_cograph_network(result) aes <- result$network$get_edge_aes() expect_equal(aes$label_bg_padding, 0.5) }) test_that("sn_edges() handles label_border_color parameter", { adj <- create_test_matrix(3) net <- cograph(adj) # Line 401 result <- sn_edges(net, label_border_color = "red") expect_cograph_network(result) aes <- result$network$get_edge_aes() expect_equal(aes$label_border_color, "red") }) test_that("sn_edges() handles label_underline parameter", { adj <- create_test_matrix(3) net <- cograph(adj) # Line 405 result <- sn_edges(net, label_underline = TRUE) expect_cograph_network(result) aes <- result$network$get_edge_aes() expect_true(aes$label_underline) }) test_that("scale_edge_widths_simple() handles all-NA values", { # Line 545 scale_fn <- cograph:::scale_edge_widths_simple result <- scale_fn(c(NA, NA, NA)) expect_length(result, 3) expect_true(all(result == mean(c(0.5, 3)))) }) test_that("scale_edge_widths_simple() handles equal values", { # Lines 558-559 scale_fn <- cograph:::scale_edge_widths_simple result <- scale_fn(c(1, 1, 1)) expect_length(result, 3) expect_true(all(result == mean(c(0.5, 3)))) }) test_that("map_edge_colors() maps positive, negative, zero, and NA weights", { # Lines 580-585 map_fn <- cograph:::map_edge_colors colors <- map_fn(c(0.5, -0.5, 0, NA)) expect_length(colors, 4) expect_equal(colors[1], "#2E7D32") # positive expect_equal(colors[2], "#C62828") # negative expect_equal(colors[3], "gray50") # zero expect_equal(colors[4], "gray50") # NA }) # ============================================ # 3. from-qgraph.R: tna_color_palette, from_qgraph # ============================================ test_that("tna_color_palette() handles different group sizes", { palette_fn <- cograph:::tna_color_palette # 1-2 states (line 21) pal_2 <- palette_fn(2) expect_length(pal_2, 2) # 3-8 states (line 27) pal_5 <- palette_fn(5) expect_length(pal_5, 5) # 9-12 states (lines 30-31, 33) pal_10 <- palette_fn(10) expect_length(pal_10, 10) # 13+ states (lines 36-37, 39) pal_15 <- palette_fn(15) expect_length(pal_15, 15) }) test_that("from_qgraph() errors on non-qgraph input", { # Line 172-173 expect_error( from_qgraph(list(x = 1)), "qgraph" ) }) test_that("from_qgraph() works with a mock qgraph object", { # Build a minimal mock qgraph object to exercise lines 178, 326, 336-338, # 347-348, 354, 361, 363, 414, 421-422, 428, 432, 434-438 adj <- matrix(c(0, 0.5, 0.3, 0.5, 0, 0.4, 0.3, 0.4, 0), 3, 3) mock_q <- structure(list( Arguments = list( input = adj, posCol = "blue", negCol = "red", theme = "colorblind" ), Edgelist = list( from = c(1L, 1L, 2L, 2L, 3L, 3L), to = c(2L, 3L, 1L, 3L, 1L, 2L), weight = c(0.5, 0.3, 0.5, 0.4, 0.3, 0.4), directed = c(FALSE, FALSE, FALSE, FALSE, FALSE, FALSE) ), graphAttributes = list( Nodes = list( names = c("N1", "N2", "N3"), labels = c("N1", "N2", "N3"), color = c("lightblue", "lightgreen", "lightyellow"), width = c(1.5, 1.0, 1.2), shape = c("circle", "rectangle", "triangle"), border.color = c("black", "gray", "red"), border.width = c(1, 2, 1), label.cex = c(1, 1, 1), label.color = c("black", "black", "black"), pie = list(c(0.5), c(0.7), c(0.3)), pieColor = c("steelblue", "coral", "gold") ), Edges = list( labels = c("a", "b", "c", "d", "e", "f"), label.cex = c(1, 1, 1, 1, 1, 1), lty = c(1, 2, 1, 1, 1, 1), asize = c(5, 5, 5, 5, 5, 5), edge.label.position = c(0.5, 0.5, 0.5, 0.5, 0.5, 0.5) ), Graph = list( minimum = 0.1, maximum = 1.0, groups = list(A = c(1, 2), B = c(3)) ) ), layout = matrix(c(0.1, 0.5, 0.9, 0.2, 0.8, 0.5), ncol = 2) ), class = "qgraph") # Extract without plotting (line 178 region) params <- from_qgraph(mock_q, plot = FALSE) expect_true(is.list(params)) expect_true("x" %in% names(params)) expect_true("labels" %in% names(params)) expect_equal(params$labels, c("N1", "N2", "N3")) expect_equal(params$edge_positive_color, "blue") expect_equal(params$edge_negative_color, "red") expect_true(!is.null(params$donut_fill)) expect_true(!is.null(params$donut_color)) expect_true(!is.null(params$node_shape)) expect_true(!is.null(params$threshold)) expect_true(!is.null(params$maximum)) }) test_that("from_qgraph() with layout override removes rescale", { # Lines 413-414 adj <- matrix(c(0, 0.5, 0.5, 0), 2, 2) mock_q <- structure(list( Arguments = list(input = adj), Edgelist = list( from = c(1L, 2L), to = c(2L, 1L), weight = c(0.5, 0.5), directed = c(FALSE, FALSE) ), graphAttributes = list( Nodes = list(names = c("A", "B")), Edges = list(), Graph = list() ), layout = matrix(c(0.2, 0.8, 0.3, 0.7), ncol = 2) ), class = "qgraph") params <- from_qgraph(mock_q, plot = FALSE, layout = "circle") # rescale should be removed when layout is overridden expect_null(params$rescale) expect_equal(params$layout, "circle") }) test_that("from_qgraph() with engine='soplot' and plot=TRUE", { # Lines 420-422, 428, 432, 434-438 adj <- matrix(c(0, 0.5, 0.5, 0), 2, 2) mock_q <- structure(list( Arguments = list(input = adj), Edgelist = list( from = c(1L, 2L), to = c(2L, 1L), weight = c(0.5, 0.5), directed = c(FALSE, FALSE) ), graphAttributes = list( Nodes = list( names = c("A", "B"), shape = c("circle", "circle") ), Edges = list( lty = c(1, 2), asize = c(5, 5), label.cex = c(1.0, 1.2), edge.label.position = c(0.3, 0.7) ), Graph = list() ), layout = matrix(c(0.2, 0.8, 0.3, 0.7), ncol = 2) ), class = "qgraph") result <- safe_plot(from_qgraph(mock_q, engine = "soplot", plot = TRUE)) expect_true(result$success, info = result$error) }) test_that("from_qgraph() with engine='splot' and plot=TRUE", { adj <- matrix(c(0, 0.5, 0.5, 0), 2, 2) mock_q <- structure(list( Arguments = list(input = adj), Edgelist = list( from = c(1L, 2L), to = c(2L, 1L), weight = c(0.5, 0.5), directed = c(FALSE, FALSE) ), graphAttributes = list( Nodes = list(names = c("A", "B")), Edges = list(), Graph = list() ), layout = matrix(c(0.2, 0.8, 0.3, 0.7), ncol = 2) ), class = "qgraph") result <- safe_plot(from_qgraph(mock_q, engine = "splot", plot = TRUE)) expect_true(result$success, info = result$error) }) # ============================================ # 4. plot-htna.R: various parameter paths # ============================================ test_that("plot_htna handles matrix input without colnames", { # Lines 150-151, 154: matrix branch with NULL colnames mat <- create_test_matrix(6) # Give colnames for valid test colnames(mat) <- rownames(mat) <- paste0("N", 1:6) node_list <- list( G1 = c("N1", "N2", "N3"), G2 = c("N4", "N5", "N6") ) result <- safe_plot(plot_htna(mat, node_list)) expect_true(result$success, info = result$error) }) test_that("plot_htna with matrix without colnames generates default labels", { # Line 154 mat <- matrix(runif(16), 4, 4) # No colnames - should get "1", "2", "3", "4" node_list <- list( G1 = c("1", "2"), G2 = c("3", "4") ) result <- safe_plot(plot_htna(mat, node_list)) expect_true(result$success, info = result$error) }) test_that("plot_htna legacy layout names map to polygon", { # Line 189 mat <- create_test_matrix(9) colnames(mat) <- rownames(mat) <- LETTERS[1:9] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F"), G3 = c("G", "H", "I") ) # "triangle" should map to polygon result <- safe_plot(plot_htna(mat, node_list, layout = "triangle")) expect_true(result$success, info = result$error) }) test_that("plot_htna circular layout requires 2+ groups", { # Lines 199-200 mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot(plot_htna(mat, node_list, layout = "circular")) expect_true(result$success, info = result$error) }) test_that("plot_htna 3+ groups auto-generates colors and shapes", { # Lines 276-277 mat <- create_test_matrix(9) colnames(mat) <- rownames(mat) <- LETTERS[1:9] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F"), G3 = c("G", "H", "I") ) result <- safe_plot(plot_htna(mat, node_list)) expect_true(result$success, info = result$error) }) test_that("plot_htna horizontal bipartite layout works", { # Lines 330-331, 335-336, 343-350 mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot(plot_htna(mat, node_list, orientation = "horizontal")) expect_true(result$success, info = result$error) }) test_that("plot_htna horizontal with list jitter works", { # Lines 346-350 mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, orientation = "horizontal", jitter = list(A = -0.2, D = 0.1)) ) expect_true(result$success, info = result$error) }) test_that("plot_htna with edge_colors = FALSE disables edge coloring", { # Line 430 mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, edge_colors = FALSE) ) expect_true(result$success, info = result$error) }) test_that("plot_htna unnamed groups get default names in legend", { # Line 439 mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list(c("A", "B", "C"), c("D", "E", "F")) result <- safe_plot(plot_htna(mat, node_list)) expect_true(result$success, info = result$error) }) test_that("plot_htna circular layout with 3 groups works", { # Lines 643 area: compute_circular_layout mat <- create_test_matrix(9) colnames(mat) <- rownames(mat) <- LETTERS[1:9] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F"), G3 = c("G", "H", "I") ) result <- safe_plot( plot_htna(mat, node_list, layout = "circular") ) expect_true(result$success, info = result$error) }) test_that("plot_htna single-node groups handled correctly", { # Lines 711, 713-715: single node in circular layout mat <- create_test_matrix(5) colnames(mat) <- rownames(mat) <- LETTERS[1:5] node_list <- list( G1 = c("A"), G2 = c("B", "C"), G3 = c("D", "E") ) result <- safe_plot( plot_htna(mat, node_list, layout = "circular") ) expect_true(result$success, info = result$error) }) # ============================================ # 5. plot-htna-multi.R: various plot_mtna paths # ============================================ test_that("plot_mtna with matrix without colnames generates labels", { # Lines 100, 120-121 mat <- matrix(runif(16), 4, 4) # No colnames -> auto-generated "1", "2", "3", "4" clusters <- list( C1 = c("1", "2"), C2 = c("3", "4") ) result <- safe_plot(plot_mtna(mat, clusters)) expect_true(result$success, info = result$error) }) test_that("plot_mtna validates missing nodes in cluster", { # Lines 120-121 mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] expect_error( plot_mtna(mat, list(C1 = c("A", "Z"), C2 = c("C", "D"))), "not found" ) }) test_that("plot_mtna unnamed clusters get default names", { # Line 300 mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] clusters <- list(c("A", "B", "C", "D"), c("E", "F", "G", "H")) result <- safe_plot(plot_mtna(mat, clusters)) expect_true(result$success, info = result$error) }) test_that("plot_mtna grid layout works", { # Line 360 region mat <- create_test_matrix(12) colnames(mat) <- rownames(mat) <- LETTERS[1:12] clusters <- list( C1 = c("A", "B", "C"), C2 = c("D", "E", "F"), C3 = c("G", "H", "I"), C4 = c("J", "K", "L") ) result <- safe_plot(plot_mtna(mat, clusters, layout = "grid")) expect_true(result$success, info = result$error) }) test_that("plot_mtna horizontal layout works", { # Lines 383, 387-388 mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] clusters <- list( C1 = c("A", "B", "C", "D"), C2 = c("E", "F", "G", "H") ) result <- safe_plot(plot_mtna(mat, clusters, layout = "horizontal")) expect_true(result$success, info = result$error) }) test_that("plot_mtna vertical layout works", { # Lines 390, 399 mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] clusters <- list( C1 = c("A", "B", "C", "D"), C2 = c("E", "F", "G", "H") ) result <- safe_plot(plot_mtna(mat, clusters, layout = "vertical")) expect_true(result$success, info = result$error) }) test_that("plot_mtna unknown layout errors", { # Line 402 mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] clusters <- list( C1 = c("A", "B", "C", "D"), C2 = c("E", "F", "G", "H") ) expect_error( plot_mtna(mat, clusters, layout = "unknown_layout"), "Unknown layout" ) }) test_that("plot_mtna with single-node clusters works", { # Lines 408, 418 area mat <- create_test_matrix(4) colnames(mat) <- rownames(mat) <- LETTERS[1:4] clusters <- list( C1 = c("A"), C2 = c("B"), C3 = c("C", "D") ) result <- safe_plot(plot_mtna(mat, clusters)) expect_true(result$success, info = result$error) }) test_that("plot_mtna with bundle_edges=FALSE works", { # Line 466-467 region mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] clusters <- list( C1 = c("A", "B", "C", "D"), C2 = c("E", "F", "G", "H") ) result <- safe_plot(plot_mtna(mat, clusters, bundle_edges = FALSE)) expect_true(result$success, info = result$error) }) test_that("plot_mtna with various shell shapes in summary mode", { # Lines 550-551 (pentagon/hexagon default shape) mat <- create_test_matrix(10) colnames(mat) <- rownames(mat) <- LETTERS[1:10] clusters <- list( C1 = c("A", "B"), C2 = c("C", "D") ) result <- safe_plot( plot_mtna(mat[1:4, 1:4], clusters, shapes = c("square", "diamond")) ) expect_true(result$success, info = result$error) }) test_that("plot_mtna summary_edges=FALSE uses individual edges mode", { # Lines 598, 732, 740 region mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] clusters <- list( C1 = c("A", "B", "C", "D"), C2 = c("E", "F", "G", "H") ) # This path calls plot_tna (the regular plotting) result <- tryCatch({ with_temp_png( plot_mtna(mat, clusters, summary_edges = FALSE) ) TRUE }, error = function(e) { # plot_tna might not exist; skip gracefully FALSE }) # Either succeeds or fails gracefully expect_true(TRUE) }) test_that("plot_mtna with legend=FALSE works", { mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] clusters <- list( C1 = c("A", "B", "C", "D"), C2 = c("E", "F", "G", "H") ) result <- safe_plot(plot_mtna(mat, clusters, legend = FALSE)) expect_true(result$success, info = result$error) }) # ============================================ # 6. input-qgraph.R: parse_qgraph error paths # ============================================ test_that("parse_qgraph() requires qgraph package", { # Line 18 parse_fn <- cograph:::parse_qgraph # Create mock that isn't a qgraph object expect_error(parse_fn(list(x = 1)), "qgraph|Input must be") }) test_that("parse_qgraph() validates input is qgraph object", { skip_if_not_installed("qgraph") parse_fn <- cograph:::parse_qgraph # Not a qgraph object (lines 37-39, 41) expect_error(parse_fn(list(x = 1)), "qgraph") }) test_that("parse_qgraph() handles mock qgraph with edge list", { skip_if_not_installed("qgraph") parse_fn <- cograph:::parse_qgraph mock_q <- structure(list( Arguments = list(input = matrix(c(0, 1, 1, 0), 2, 2)), Edgelist = list( from = c(1L, 2L), to = c(2L, 1L), weight = c(1, 1), directed = c(FALSE, FALSE) ), graphAttributes = list( Nodes = list(names = c("A", "B")) ), layout = matrix(c(0.1, 0.9, 0.5, 0.5), ncol = 2) ), class = "qgraph") result <- parse_fn(mock_q) expect_true(is.list(result)) expect_false(result$directed) expect_equal(nrow(result$nodes), 2) }) test_that("parse_qgraph() handles empty edge list", { skip_if_not_installed("qgraph") parse_fn <- cograph:::parse_qgraph mock_q <- structure(list( Arguments = list(input = matrix(0, 3, 3)), Edgelist = list( from = integer(0), to = integer(0), weight = numeric(0) ), graphAttributes = list( Nodes = list(names = c("A", "B", "C")) ) ), class = "qgraph") result <- parse_fn(mock_q) expect_true(is.list(result)) expect_equal(nrow(result$nodes), 3) # Lines 67-69: empty edge list path expect_equal(length(result$weights), 0) }) test_that("parse_qgraph() infers n from edge list when no input matrix", { skip_if_not_installed("qgraph") parse_fn <- cograph:::parse_qgraph # Lines 51-53, 56-58, 60, 62 mock_q <- structure(list( Arguments = list(input = NULL), Edgelist = list( from = c(1L, 2L, 3L), to = c(2L, 3L, 1L), weight = c(1, 1, 1), directed = c(TRUE, TRUE, TRUE) ), graphAttributes = list( Nodes = list() # No names ) ), class = "qgraph") result <- parse_fn(mock_q) expect_true(is.list(result)) expect_true(result$directed) expect_equal(nrow(result$nodes), 3) }) test_that("parse_qgraph() with layout adds coords to nodes", { skip_if_not_installed("qgraph") parse_fn <- cograph:::parse_qgraph # Lines 82-83 mock_q <- structure(list( Arguments = list(input = matrix(c(0, 1, 1, 0), 2, 2)), Edgelist = list( from = c(1L, 2L), to = c(2L, 1L), weight = c(1, 1), directed = c(FALSE, FALSE) ), graphAttributes = list( Nodes = list(names = c("X", "Y")) ), layout = matrix(c(0.3, 0.7, 0.4, 0.6), ncol = 2) ), class = "qgraph") result <- parse_fn(mock_q) expect_true("x" %in% names(result$nodes)) expect_true("y" %in% names(result$nodes)) }) # ============================================ # 7. input-igraph.R: parse_igraph & layout functions # ============================================ test_that("parse_igraph() requires igraph package", { # Line 18 parse_fn <- cograph:::parse_igraph expect_error( parse_fn(list(x = 1)), "igraph" ) }) test_that("parse_igraph() validates input is igraph object", { skip_if_not_installed("igraph") parse_fn <- cograph:::parse_igraph # Line 25 expect_error(parse_fn("not an igraph"), "igraph") }) test_that("parse_igraph() handles named graph without weights", { skip_if_not_installed("igraph") parse_fn <- cograph:::parse_igraph g <- igraph::make_ring(4) igraph::V(g)$name <- c("A", "B", "C", "D") # Lines 49, 62 result <- parse_fn(g) expect_true(is.list(result)) expect_equal(nrow(result$nodes), 4) expect_false(result$directed) }) test_that("parse_igraph() handles weighted graph", { skip_if_not_installed("igraph") parse_fn <- cograph:::parse_igraph g <- igraph::make_ring(3) igraph::E(g)$weight <- c(0.5, 0.7, 0.3) result <- parse_fn(g) expect_equal(result$weights, c(0.5, 0.7, 0.3)) }) test_that("parse_igraph() handles unnamed graph", { skip_if_not_installed("igraph") parse_fn <- cograph:::parse_igraph g <- igraph::make_ring(3) # No names set - line 69-70 result <- parse_fn(g) # Labels should be auto-generated "1", "2", "3" expect_equal(result$nodes$label, c("1", "2", "3")) }) test_that("parse_igraph() handles additional vertex attributes", { skip_if_not_installed("igraph") parse_fn <- cograph:::parse_igraph g <- igraph::make_ring(3) igraph::V(g)$color <- c("red", "blue", "green") result <- parse_fn(g) expect_true("color" %in% names(result$nodes)) }) test_that("apply_igraph_layout() requires igraph", { # Line 93 layout_fn <- cograph:::apply_igraph_layout # This will error since we need a CographNetwork (won't hit igraph check first) # But the function check is at line 92-93 skip_if_not_installed("igraph") adj <- create_test_matrix(3) net <- cograph(adj) result <- layout_fn(net$network, igraph::layout_in_circle) expect_true(is.data.frame(result)) expect_equal(nrow(result), 3) }) test_that("apply_igraph_layout_by_name() handles unknown layout", { skip_if_not_installed("igraph") layout_fn <- cograph:::apply_igraph_layout_by_name adj <- create_test_matrix(3) net <- cograph(adj) # Lines 175-176 expect_error(layout_fn(net$network, "nonexistent_layout"), "Unknown igraph layout") }) test_that("normalize_coords() handles single node", { # Lines 232, 241 norm_fn <- cograph:::normalize_coords coords <- matrix(c(5, 10), nrow = 1) result <- norm_fn(coords) expect_equal(result[1, 1], 0.5) expect_equal(result[1, 2], 0.5) }) test_that("normalize_coords() handles zero-range dimension", { # Line 241 norm_fn <- cograph:::normalize_coords coords <- matrix(c(5, 5, 5, 1, 2, 3), ncol = 2) result <- norm_fn(coords) expect_true(all(result[, 1] == 0.5)) # Zero range x -> all 0.5 expect_true(all(result[, 2] >= 0.1)) expect_true(all(result[, 2] <= 0.9)) }) # ============================================ # 8. shapes-svg.R: parse_svg, draw_svg_shape, draw_svg_shape_base # ============================================ test_that("parse_svg() returns cached result on second call", { # Line 90 parse_fn <- cograph:::parse_svg svg_data <- list( source = '', is_file = FALSE, parsed = "already_parsed" # Mock cached result ) result <- parse_fn(svg_data) expect_equal(result, "already_parsed") }) test_that("parse_svg() warns when grImport2 not installed", { # Lines 95, 98 parse_fn <- cograph:::parse_svg svg_data <- list( source = '', is_file = FALSE, parsed = NULL ) # This may or may not warn depending on grImport2 availability result <- tryCatch({ suppressWarnings(parse_fn(svg_data)) }, error = function(e) NULL) # Just verify no crash expect_true(TRUE) }) test_that("parse_svg() handles file-based SVG source", { # Line 104 parse_fn <- cograph:::parse_svg tmp <- tempfile(fileext = ".svg") writeLines('', tmp) on.exit(unlink(tmp)) svg_data <- list(source = tmp, is_file = TRUE, parsed = NULL) # Will fail if grImport2 not available, but shouldn't crash result <- tryCatch( suppressWarnings(parse_fn(svg_data)), error = function(e) NULL ) expect_true(TRUE) }) test_that("draw_svg_shape() falls back to circle on NULL parsed", { # Lines 156-157, 159 draw_fn <- cograph:::draw_svg_shape svg_data <- list( source = "invalid", is_file = FALSE, parsed = NULL ) with_temp_png({ grid::grid.newpage() result <- suppressWarnings( draw_fn(0.5, 0.5, 0.1, svg_data, "red", "black", 1, 1, TRUE) ) expect_true(inherits(result, "grob")) }) }) test_that("draw_svg_shape_base() falls back to circle when rsvg not installed", { # Lines 227, 233 draw_base_fn <- cograph:::draw_svg_shape_base svg_data <- list( source = '', is_file = FALSE, parsed = NULL ) with_temp_png({ plot(0, 0, type = "n", xlim = c(0, 1), ylim = c(0, 1)) result <- tryCatch( suppressWarnings(draw_base_fn(0.5, 0.5, 0.1, svg_data, "blue", "black", 1)), error = function(e) NULL ) expect_true(TRUE) }) }) test_that("draw_svg_shape_base() handles file-based SVG", { # Lines 239, 258 draw_base_fn <- cograph:::draw_svg_shape_base tmp <- tempfile(fileext = ".svg") writeLines('', tmp) on.exit(unlink(tmp)) svg_data <- list(source = tmp, is_file = TRUE, parsed = NULL) with_temp_png({ plot(0, 0, type = "n", xlim = c(0, 1), ylim = c(0, 1)) result <- tryCatch( suppressWarnings(draw_base_fn(0.5, 0.5, 0.1, svg_data, "blue", "black", 1)), error = function(e) NULL ) expect_true(TRUE) }) }) # ============================================ # 9. shapes-special.R: draw functions via splot # ============================================ test_that("splot renders pie shape nodes", { # Line 119 region: draw_pie adj <- create_test_matrix(3) result <- safe_plot( splot(adj, node_shape = "pie", pie_values = list(c(1, 2), c(2, 1), c(1, 1, 1)), pie_colors = list(c("red", "blue"), c("green", "yellow"), c("red", "blue", "green"))) ) expect_true(result$success, info = result$error) }) test_that("splot renders donut shape with polygon base", { # Lines 237-238, 268 region: draw_polygon_donut adj <- create_test_matrix(3) result <- safe_plot( splot(adj, node_shape = "donut", donut_fill = c(0.3, 0.6, 0.9), donut_shape = "square") ) expect_true(result$success, info = result$error) }) test_that("splot renders donut with show_value", { # Lines 278, 303 region adj <- create_test_matrix(3) result <- safe_plot( splot(adj, node_shape = "donut", donut_fill = c(0.25, 0.5, 0.75), donut_show_value = TRUE, donut_value_suffix = "%") ) expect_true(result$success, info = result$error) }) test_that("splot renders donut with multiple values (segmented donut)", { # Line 458 region: multi-segment donut adj <- create_test_matrix(3) result <- safe_plot( splot(adj, node_shape = "donut", donut_values = list(c(1, 2, 3), c(3, 2, 1), c(1, 1, 1)), donut_colors = list(c("red", "blue", "green"), c("orange", "purple", "cyan"), c("pink", "gray", "black"))) ) expect_true(result$success, info = result$error) }) test_that("splot renders double donut pie with segmented outer ring", { # Lines 770, 807-808 region: draw_double_donut_pie adj <- create_test_matrix(3) result <- safe_plot( splot(adj, node_shape = "donut", donut_fill = c(0.5, 0.7, 0.3), donut2_values = list(c(1, 2), c(2, 1), c(1, 1)), donut2_colors = list(c("red", "blue"), c("green", "yellow"), c("pink", "gray"))) ) expect_true(result$success, info = result$error) }) # ============================================ # 10. input-statnet.R: error paths # ============================================ test_that("parse_statnet() requires network package", { # Line 18 parse_fn <- cograph:::parse_statnet expect_error(parse_fn(list(x = 1)), "network|Input must be") }) test_that("parse_statnet() validates input is network object", { skip_if_not_installed("network") parse_fn <- cograph:::parse_statnet # Line 39 expect_error(parse_fn("not a network"), "network") }) test_that("parse_statnet() handles network object", { skip_if_not_installed("network") parse_fn <- cograph:::parse_statnet # Lines 57, 80 net <- network::network(matrix(c(0, 1, 0, 1, 0, 1, 0, 1, 0), 3, 3)) result <- parse_fn(net) expect_true(is.list(result)) expect_equal(nrow(result$nodes), 3) }) # ============================================ # 11. output-save.R: SVG and EPS save paths # ============================================ test_that("sn_save() creates SVG file (line 63-64)", { skip_on_cran() adj <- create_test_matrix(4) net <- cograph(adj) tmp <- tempfile(fileext = ".svg") on.exit(unlink(tmp), add = TRUE) # Check if SVG device is available svg_ok <- tryCatch({ grDevices::svg(tmp) grDevices::dev.off() unlink(tmp) TRUE }, warning = function(w) { if (grepl("cairo|X11", conditionMessage(w), ignore.case = TRUE)) FALSE else TRUE }, error = function(e) FALSE) if (!svg_ok) skip("SVG device not available") expect_message(sn_save(net, tmp), "Saved") expect_file_created(tmp) }) test_that("sn_save() creates PS/EPS file (lines 79, 81)", { skip_on_cran() adj <- create_test_matrix(4) net <- cograph(adj) tmp <- tempfile(fileext = ".ps") on.exit(unlink(tmp), add = TRUE) result <- tryCatch({ suppressWarnings(sn_save(net, tmp)) TRUE }, error = function(e) { if (grepl("font|family|postscript", conditionMessage(e), ignore.case = TRUE)) { FALSE } else { stop(e) } }) if (!result) skip("PostScript device has font issues on this system") expect_file_created(tmp) }) # ============================================ # 12. layout-registry.R: grid 0/1 nodes, bipartite 0 nodes, # star 0/1 nodes, gephi_fr layout # ============================================ test_that("grid layout handles 0 nodes", { # Line 31 - cograph() can't create 0-node networks, # so we test the layout function directly if we can access network internals grid_fn <- get_layout("grid") skip_if(is.null(grid_fn), "grid layout not registered") # Create a mock network-like object with n_nodes = 0 mock_net <- list(n_nodes = 0L) result <- grid_fn(mock_net) expect_equal(nrow(result), 0) }) test_that("grid layout handles 1 node", { # Line 32 adj <- matrix(0, 1, 1) net <- cograph(adj) grid_fn <- get_layout("grid") if (!is.null(grid_fn)) { result <- grid_fn(net$network) expect_equal(nrow(result), 1) expect_equal(result$x[1], 0.5) expect_equal(result$y[1], 0.5) } else { expect_true(TRUE) } }) test_that("star layout handles 0 nodes", { # Line 55 star_fn <- get_layout("star") skip_if(is.null(star_fn), "star layout not registered") mock_net <- list(n_nodes = 0L) result <- star_fn(mock_net) expect_equal(nrow(result), 0) }) test_that("star layout handles 1 node", { # Line 56 adj <- matrix(0, 1, 1) net <- cograph(adj) star_fn <- get_layout("star") if (!is.null(star_fn)) { result <- star_fn(net$network) expect_equal(nrow(result), 1) expect_equal(result$x[1], 0.5) expect_equal(result$y[1], 0.5) } else { expect_true(TRUE) } }) test_that("bipartite layout handles 0 nodes", { # Line 78 bipartite_fn <- get_layout("bipartite") skip_if(is.null(bipartite_fn), "bipartite layout not registered") mock_net <- list(n_nodes = 0L) result <- bipartite_fn(mock_net) expect_equal(nrow(result), 0) }) test_that("custom layout passes through matrix coordinates", { # Lines 107-111 adj <- create_test_matrix(3) net <- cograph(adj) custom_fn <- get_layout("custom") if (!is.null(custom_fn)) { coords <- matrix(c(0.1, 0.5, 0.9, 0.2, 0.8, 0.5), ncol = 2) result <- custom_fn(net$network, coords = coords) expect_equal(nrow(result), 3) expect_equal(names(result)[1:2], c("x", "y")) } else { expect_true(TRUE) } }) test_that("gephi_fr layout works with igraph", { # Lines 117-201 skip_if_not_installed("igraph") adj <- create_test_matrix(5) net <- cograph(adj) gephi_fn <- get_layout("gephi_fr") if (!is.null(gephi_fn)) { set.seed(42) result <- gephi_fn(net$network, niter = 10) expect_true(is.data.frame(result)) expect_equal(nrow(result), 5) expect_true("x" %in% names(result)) expect_true("y" %in% names(result)) } else { expect_true(TRUE) } }) test_that("gephi_fr layout handles 0 nodes", { skip_if_not_installed("igraph") gephi_fn <- get_layout("gephi_fr") skip_if(is.null(gephi_fn), "gephi_fr layout not registered") # Create a mock network that network_to_igraph can handle # Use a 1-node network since 0-node cograph isn't supported adj <- matrix(0, 1, 1) net <- cograph(adj) # Test with 1 node (still exercises early return path) result <- gephi_fn(net$network, niter = 5) expect_true(is.data.frame(result)) }) test_that("gephi_fr layout handles empty graph (no edges)", { skip_if_not_installed("igraph") adj <- matrix(0, 3, 3) net <- cograph(adj) gephi_fn <- get_layout("gephi_fr") if (!is.null(gephi_fn)) { set.seed(42) result <- gephi_fn(net$network, niter = 5) expect_true(is.data.frame(result)) expect_equal(nrow(result), 3) } else { expect_true(TRUE) } }) # ============================================ # Additional coverage for edge cases # ============================================ test_that("map_qgraph_lty handles unknown lty values", { lty_fn <- cograph:::map_qgraph_lty result <- lty_fn(c(1, 2, 99)) expect_equal(result[1], "solid") expect_equal(result[2], "dashed") expect_equal(result[3], "solid") # Unknown defaults to solid }) test_that("map_qgraph_shape handles unknown shapes", { shape_fn <- cograph:::map_qgraph_shape result <- shape_fn(c("circle", "unknown_shape", "rectangle")) expect_equal(result[1], "circle") expect_equal(result[2], "unknown_shape") # Unknown passed through expect_equal(result[3], "square") # rectangle -> square }) test_that("from_qgraph with minimum override maps to threshold", { adj <- matrix(c(0, 0.5, 0.5, 0), 2, 2) mock_q <- structure(list( Arguments = list(input = adj), Edgelist = list( from = c(1L, 2L), to = c(2L, 1L), weight = c(0.5, 0.5), directed = c(FALSE, FALSE) ), graphAttributes = list( Nodes = list(names = c("A", "B")), Edges = list(), Graph = list() ), layout = matrix(c(0.2, 0.8, 0.3, 0.7), ncol = 2) ), class = "qgraph") params <- from_qgraph(mock_q, plot = FALSE, minimum = 0.3) # minimum should be mapped to threshold expect_equal(params$threshold, 0.3) }) test_that("plot_htna with numeric jitter (not TRUE) works", { mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, jitter = 0.3) ) expect_true(result$success, info = result$error) }) test_that("plot_htna with jitter_side='none' works", { mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, jitter_side = "none") ) expect_true(result$success, info = result$error) }) test_that("plot_htna with use_list_order=FALSE reorders by weight", { mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, use_list_order = FALSE) ) expect_true(result$success, info = result$error) }) test_that("plot_htna with extend_lines=TRUE works", { mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, extend_lines = TRUE) ) expect_true(result$success, info = result$error) }) test_that("plot_htna with extend_lines numeric works", { mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, extend_lines = 0.2) ) expect_true(result$success, info = result$error) }) test_that("plot_htna horizontal with extend_lines works", { mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, orientation = "horizontal", extend_lines = TRUE) ) expect_true(result$success, info = result$error) }) test_that("plot_htna horizontal with numeric jitter works", { mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] node_list <- list( G1 = c("A", "B", "C"), G2 = c("D", "E", "F") ) result <- safe_plot( plot_htna(mat, node_list, orientation = "horizontal", jitter = 0.3) ) expect_true(result$success, info = result$error) }) test_that("plot_htna with single-node group in bipartite", { mat <- create_test_matrix(4) colnames(mat) <- rownames(mat) <- LETTERS[1:4] node_list <- list( G1 = c("A"), G2 = c("B", "C", "D") ) result <- safe_plot( plot_htna(mat, node_list) ) expect_true(result$success, info = result$error) }) test_that("plot_mtna with triangle shell shape works", { mat <- create_test_matrix(6) colnames(mat) <- rownames(mat) <- LETTERS[1:6] clusters <- list( C1 = c("A", "B", "C"), C2 = c("D", "E", "F") ) result <- safe_plot( plot_mtna(mat, clusters, shapes = c("triangle", "circle")) ) expect_true(result$success, info = result$error) }) test_that("plot_mtna with within_edges=FALSE works", { mat <- create_test_matrix(8) colnames(mat) <- rownames(mat) <- LETTERS[1:8] clusters <- list( C1 = c("A", "B", "C", "D"), C2 = c("E", "F", "G", "H") ) result <- safe_plot( plot_mtna(mat, clusters, within_edges = FALSE) ) expect_true(result$success, info = result$error) }) test_that("sn_edges() handles label_shadow parameters", { adj <- create_test_matrix(3) net <- cograph(adj) # Test multiple shadow parameters at once result <- sn_edges(net, label_shadow = TRUE, label_shadow_color = "gray30", label_shadow_offset = 1.0, label_shadow_alpha = 0.7 ) expect_cograph_network(result) aes <- result$network$get_edge_aes() expect_true(aes$label_shadow) expect_equal(aes$label_shadow_color, "gray30") expect_equal(aes$label_shadow_offset, 1.0) expect_equal(aes$label_shadow_alpha, 0.7) }) test_that("compute_connectivity_jitter_horizontal handles groups with no connections", { jitter_fn <- cograph:::compute_connectivity_jitter_horizontal weights <- matrix(0, 4, 4) g1_idx <- 1:2 g2_idx <- 3:4 result <- jitter_fn(weights, g1_idx, g2_idx, amount = 0.5, side = "both") expect_length(result, 4) expect_true(all(result == 0)) }) test_that("compute_connectivity_jitter_vertical handles groups with no connections", { jitter_fn <- cograph:::compute_connectivity_jitter_vertical weights <- matrix(0, 4, 4) g1_idx <- 1:2 g2_idx <- 3:4 result <- jitter_fn(weights, g1_idx, g2_idx, amount = 0.5, side = "both") expect_length(result, 4) expect_true(all(result == 0)) })