tests/testthat/test-coverage-input-igraph-42.R

# =============================================================================
# Test Coverage for input-igraph.R
# =============================================================================
# Comprehensive tests targeting uncovered functions and branches
# Tests all igraph input parsing and layout functions

# Make internal functions available for testing
skip_on_cran()

parse_igraph <- cograph:::parse_igraph
apply_igraph_layout <- cograph:::apply_igraph_layout
apply_igraph_layout_by_name <- cograph:::apply_igraph_layout_by_name
network_to_igraph <- cograph:::network_to_igraph
normalize_coords <- cograph:::normalize_coords
create_nodes_df <- cograph:::create_nodes_df
create_edges_df <- cograph:::create_edges_df

# =============================================================================
# Test: parse_igraph() - Basic Functionality
# =============================================================================

test_that("parse_igraph parses simple undirected graph", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(5)
  result <- parse_igraph(g)

  expect_true(is.list(result))
  expect_true("nodes" %in% names(result))
  expect_true("edges" %in% names(result))
  expect_true("directed" %in% names(result))
  expect_true("weights" %in% names(result))
  expect_equal(nrow(result$nodes), 5)
  expect_false(result$directed)
})

test_that("parse_igraph parses directed graph correctly", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(4, directed = TRUE)
  result <- parse_igraph(g)

  expect_true(result$directed)
  expect_equal(nrow(result$nodes), 4)
})

test_that("parse_igraph respects forced directed parameter", {
  skip_if_not_installed("igraph")

  # Create undirected graph but force directed = TRUE
  g <- igraph::make_ring(3)
  result <- parse_igraph(g, directed = TRUE)

  expect_true(result$directed)
})

test_that("parse_igraph respects forced undirected parameter", {
  skip_if_not_installed("igraph")

  # Create directed graph but force directed = FALSE
  g <- igraph::make_ring(3, directed = TRUE)
  result <- parse_igraph(g, directed = FALSE)

  expect_false(result$directed)
})

# =============================================================================
# Test: parse_igraph() - Node Labels Handling
# =============================================================================

test_that("parse_igraph uses default labels when names are null", {
  skip_if_not_installed("igraph")

  # Create graph without vertex names
  g <- igraph::make_empty_graph(4)
  result <- parse_igraph(g)

  expect_equal(result$nodes$label, c("1", "2", "3", "4"))
})

test_that("parse_igraph uses vertex names as labels", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(3)
  igraph::V(g)$name <- c("Alice", "Bob", "Carol")
  result <- parse_igraph(g)

  expect_equal(result$nodes$label, c("Alice", "Bob", "Carol"))
})

test_that("parse_igraph handles NA vertex names", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(3)
  igraph::V(g)$name <- c(NA, NA, NA)
  result <- parse_igraph(g)

  # Should fall back to numeric labels

  expect_equal(result$nodes$label, c("1", "2", "3"))
})

test_that("parse_igraph handles mixed NA/valid vertex names", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(3)
  # Setting partial NA names - igraph keeps them
  igraph::V(g)$name <- c("A", NA, "C")
  result <- parse_igraph(g)

  # Names vector exists but has some NAs - should use names
  expect_true(!all(is.na(result$nodes$label)))
})

# =============================================================================
# Test: parse_igraph() - Edge Weights
# =============================================================================

test_that("parse_igraph extracts edge weights", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(4)
  igraph::E(g)$weight <- c(0.5, 1.0, 1.5, 2.0)
  result <- parse_igraph(g)

  expect_equal(result$weights, c(0.5, 1.0, 1.5, 2.0))
  expect_true("weight" %in% names(result$edges))
})

test_that("parse_igraph uses default weights when not present", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(3)
  result <- parse_igraph(g)

  # All weights should be 1
  expect_true(all(result$weights == 1))
})

test_that("parse_igraph handles graph with zero edges", {
  skip_if_not_installed("igraph")

  g <- igraph::make_empty_graph(5)
  result <- parse_igraph(g)

  expect_equal(nrow(result$nodes), 5)
  expect_equal(nrow(result$edges), 0)
  expect_equal(length(result$weights), 0)
})

# =============================================================================
# Test: parse_igraph() - Vertex Attributes
# =============================================================================

test_that("parse_igraph copies additional vertex attributes", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(3)
  igraph::V(g)$color <- c("red", "green", "blue")
  igraph::V(g)$size <- c(10, 20, 30)
  result <- parse_igraph(g)

  expect_true("color" %in% names(result$nodes))
  expect_true("size" %in% names(result$nodes))
  expect_equal(result$nodes$color, c("red", "green", "blue"))
  expect_equal(result$nodes$size, c(10, 20, 30))
})

test_that("parse_igraph skips 'name' vertex attribute (already used as label)", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(3)
  igraph::V(g)$name <- c("A", "B", "C")
  igraph::V(g)$group <- c(1, 1, 2)
  result <- parse_igraph(g)

  # 'group' should be copied, but 'name' should not appear twice
  expect_true("group" %in% names(result$nodes))
  expect_equal(result$nodes$label, c("A", "B", "C"))
})

# =============================================================================
# Test: parse_igraph() - Edge Attributes
# =============================================================================

test_that("parse_igraph copies additional edge attributes", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(3)
  igraph::E(g)$color <- c("red", "green", "blue")
  igraph::E(g)$lty <- c(1, 2, 3)
  result <- parse_igraph(g)

  expect_true("color" %in% names(result$edges))
  expect_true("lty" %in% names(result$edges))
  expect_equal(result$edges$color, c("red", "green", "blue"))
})

test_that("parse_igraph skips 'weight' edge attribute (already extracted)", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(3)
  igraph::E(g)$weight <- c(0.5, 1.0, 1.5)
  igraph::E(g)$type <- c("A", "B", "C")
  result <- parse_igraph(g)

  # 'type' should be copied
  expect_true("type" %in% names(result$edges))
  # Weight should be in edges df and also in weights vector
  expect_equal(result$edges$weight, c(0.5, 1.0, 1.5))
})

# =============================================================================
# Test: parse_igraph() - Error Handling
# =============================================================================

test_that("parse_igraph errors on non-igraph input", {
  skip_if_not_installed("igraph")

  expect_error(parse_igraph("not an igraph"), "Input must be an igraph object")
  expect_error(parse_igraph(list(a = 1)), "Input must be an igraph object")
  expect_error(parse_igraph(matrix(1:4, 2, 2)), "Input must be an igraph object")
})

# =============================================================================
# Test: network_to_igraph() - Conversion
# =============================================================================

test_that("network_to_igraph converts cograph_network to igraph", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  rownames(mat) <- colnames(mat) <- c("A", "B", "C")
  net <- as_cograph(mat)

  g <- network_to_igraph(net)

  expect_true(inherits(g, "igraph"))
  expect_equal(igraph::vcount(g), 3)
})

test_that("network_to_igraph converts CographNetwork (R6) to igraph", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 0, 1, 0, 1, 0, 1, 0), nrow = 3)
  net <- CographNetwork$new(mat)

  g <- network_to_igraph(net)

  expect_true(inherits(g, "igraph"))
  expect_equal(igraph::vcount(g), 3)
})

test_that("network_to_igraph handles empty edges", {
  skip_if_not_installed("igraph")

  # Create network with no edges
  mat <- matrix(0, 3, 3)
  rownames(mat) <- colnames(mat) <- c("A", "B", "C")
  net <- as_cograph(mat)

  g <- network_to_igraph(net)

  expect_true(inherits(g, "igraph"))
  expect_equal(igraph::vcount(g), 3)
  expect_equal(igraph::ecount(g), 0)
})

test_that("network_to_igraph preserves edge weights", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 0.5, 0.3, 0.5, 0, 0.8, 0.3, 0.8, 0), nrow = 3)
  net <- as_cograph(mat)

  g <- network_to_igraph(net)

  expect_true("weight" %in% igraph::edge_attr_names(g))
})

test_that("network_to_igraph preserves directedness", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 0, 0, 0, 1, 0, 0, 0), nrow = 3)
  net <- as_cograph(mat, directed = TRUE)

  g <- network_to_igraph(net)

  expect_true(igraph::is_directed(g))
})

test_that("network_to_igraph preserves node labels", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  rownames(mat) <- colnames(mat) <- c("Node1", "Node2")
  net <- as_cograph(mat)

  g <- network_to_igraph(net)

  expect_equal(igraph::V(g)$name, c("Node1", "Node2"))
})

test_that("network_to_igraph errors on invalid input", {
  skip_if_not_installed("igraph")

  expect_error(network_to_igraph("not a network"),
               "network must be a CographNetwork or cograph_network object")
  expect_error(network_to_igraph(list(a = 1)),
               "network must be a CographNetwork or cograph_network object")
})

# =============================================================================
# Test: normalize_coords() - Coordinate Normalization
# =============================================================================

test_that("normalize_coords normalizes to 0.1-0.9 range", {
  coords <- matrix(c(0, 100, 50, 0, 100, 50), ncol = 2)
  result <- normalize_coords(coords)

  expect_true(all(result >= 0.1))
  expect_true(all(result <= 0.9))
  # Check the range is properly normalized
  expect_equal(min(result[, 1]), 0.1)
  expect_equal(max(result[, 1]), 0.9)
})

test_that("normalize_coords handles single node", {
  coords <- matrix(c(5, 10), nrow = 1)
  result <- normalize_coords(coords)

  expect_equal(nrow(result), 1)
  expect_equal(result[1, 1], 0.5)
  expect_equal(result[1, 2], 0.5)
})

test_that("normalize_coords handles zero range in one dimension", {
  # All x coordinates are the same
  coords <- matrix(c(5, 5, 5, 0, 50, 100), ncol = 2)
  result <- normalize_coords(coords)

  # X should be 0.5 for all (zero range)
  expect_equal(result[, 1], c(0.5, 0.5, 0.5))
  # Y should be normalized
  expect_equal(result[, 2], c(0.1, 0.5, 0.9))
})

test_that("normalize_coords handles negative coordinates", {
  coords <- matrix(c(-100, 0, 100, -50, 0, 50), ncol = 2)
  result <- normalize_coords(coords)

  expect_true(all(result >= 0.1))
  expect_true(all(result <= 0.9))
})

test_that("normalize_coords handles all same values", {
  coords <- matrix(c(5, 5, 5, 5, 5, 5), ncol = 2)
  result <- normalize_coords(coords)

  # All should be centered at 0.5
  expect_equal(unique(result[, 1]), 0.5)
  expect_equal(unique(result[, 2]), 0.5)
})

# =============================================================================
# Test: apply_igraph_layout_by_name() - Layout Name Mapping
# =============================================================================

test_that("apply_igraph_layout_by_name works with two-letter aliases", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- as_cograph(mat)

  # Test circle layout
  coords_ci <- apply_igraph_layout_by_name(net, "ci", seed = 42)
  expect_equal(nrow(coords_ci), 3)
  expect_true(all(c("x", "y") %in% names(coords_ci)))

  # Test star layout
  coords_st <- apply_igraph_layout_by_name(net, "st", seed = 42)
  expect_equal(nrow(coords_st), 3)
})

test_that("apply_igraph_layout_by_name works with full names", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- as_cograph(mat)

  coords <- apply_igraph_layout_by_name(net, "layout_nicely", seed = 42)
  expect_equal(nrow(coords), 3)
  expect_true(all(c("x", "y") %in% names(coords)))
})

test_that("apply_igraph_layout_by_name works with igraph_ prefix aliases", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- as_cograph(mat)

  coords <- apply_igraph_layout_by_name(net, "igraph_circle", seed = 42)
  expect_equal(nrow(coords), 3)
})

test_that("apply_igraph_layout_by_name errors on unknown layout", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- as_cograph(mat)

  expect_error(apply_igraph_layout_by_name(net, "unknown_layout"),
               "Unknown igraph layout")
})

test_that("apply_igraph_layout_by_name respects seed for determinism", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0), nrow = 4)
  net <- as_cograph(mat)

  # Same seed should give same results
  coords1 <- apply_igraph_layout_by_name(net, "fr", seed = 123)
  coords2 <- apply_igraph_layout_by_name(net, "fr", seed = 123)

  expect_equal(coords1$x, coords2$x, tolerance = 1e-10)
  expect_equal(coords1$y, coords2$y, tolerance = 1e-10)
})

test_that("apply_igraph_layout_by_name works with NULL seed", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- as_cograph(mat)

  # NULL seed should work without error
  coords <- apply_igraph_layout_by_name(net, "ci", seed = NULL)
  expect_equal(nrow(coords), 3)
})

# =============================================================================
# Test: apply_igraph_layout() - Direct Layout Function
# =============================================================================

test_that("apply_igraph_layout applies layout function directly", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- as_cograph(mat)

  coords <- apply_igraph_layout(net, igraph::layout_in_circle)

  expect_equal(nrow(coords), 3)
  expect_true(all(c("x", "y") %in% names(coords)))
  expect_true(all(coords$x >= 0.1 & coords$x <= 0.9))
  expect_true(all(coords$y >= 0.1 & coords$y <= 0.9))
})

test_that("apply_igraph_layout passes additional arguments", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- as_cograph(mat)

  # layout_with_fr accepts niter parameter
  coords <- apply_igraph_layout(net, igraph::layout_with_fr, niter = 10)

  expect_equal(nrow(coords), 3)
})

# =============================================================================
# Test: Layout Aliases Comprehensive
# =============================================================================

test_that("all two-letter layout aliases are valid", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- as_cograph(mat)

  aliases <- c("kk", "fr", "mds", "st", "gr", "rd", "ni", "ci")

  for (alias in aliases) {
    coords <- apply_igraph_layout_by_name(net, alias, seed = 42)
    expect_equal(nrow(coords), 3,
                 info = paste("Failed for alias:", alias))
  }
})

test_that("layout_with_drl works with larger network", {
  skip_if_not_installed("igraph")

  # DRL needs more nodes to work properly
  mat <- create_test_matrix(n = 10, density = 0.3, seed = 42)
  net <- as_cograph(mat)

  # DRL may warn on small graphs but should work
  coords <- suppressWarnings(
    apply_igraph_layout_by_name(net, "drl", seed = 42)
  )

  expect_equal(nrow(coords), 10)
})

test_that("layout_with_lgl works with larger connected network", {
  skip_if_not_installed("igraph")

  # LGL needs connected graph
  mat <- create_test_topology("complete", n = 6)
  net <- as_cograph(mat)

  # LGL may produce warnings on some configurations
  coords <- suppressWarnings(
    apply_igraph_layout_by_name(net, "lgl", seed = 42)
  )

  expect_equal(nrow(coords), 6)
})

test_that("layout_as_tree works with tree-like structure", {
  skip_if_not_installed("igraph")

  # Create a star topology (tree-like)
  mat <- create_test_topology("star", n = 5)
  net <- as_cograph(mat)

  coords <- apply_igraph_layout_by_name(net, "tr", seed = 42)

  expect_equal(nrow(coords), 5)
})

test_that("layout_on_grid arranges nodes in grid", {
  skip_if_not_installed("igraph")

  mat <- create_test_matrix(n = 9, density = 0.3)
  net <- as_cograph(mat)

  coords <- apply_igraph_layout_by_name(net, "gr", seed = 42)

  expect_equal(nrow(coords), 9)
})

test_that("layout_randomly produces random layout", {
  skip_if_not_installed("igraph")

  mat <- create_test_matrix(n = 5, density = 0.5)
  net <- as_cograph(mat)

  coords <- apply_igraph_layout_by_name(net, "rd", seed = 42)

  expect_equal(nrow(coords), 5)
})

test_that("layout_on_sphere works", {
  skip_if_not_installed("igraph")

  mat <- create_test_matrix(n = 5, density = 0.5)
  net <- as_cograph(mat)

  # Sphere layout returns 3D coords, but we only use x,y
  coords <- apply_igraph_layout_by_name(net, "sp", seed = 42)

  expect_equal(nrow(coords), 5)
})

test_that("layout_with_graphopt works", {
  skip_if_not_installed("igraph")

  mat <- create_test_matrix(n = 6, density = 0.4)
  net <- as_cograph(mat)

  coords <- apply_igraph_layout_by_name(net, "go", seed = 42)

  expect_equal(nrow(coords), 6)
})

# =============================================================================
# Test: Round-Trip Conversion
# =============================================================================

test_that("igraph to cograph and back preserves structure", {
  skip_if_not_installed("igraph")

  # Create original igraph
  g_orig <- igraph::make_ring(5)
  igraph::V(g_orig)$name <- LETTERS[1:5]
  igraph::E(g_orig)$weight <- c(0.1, 0.2, 0.3, 0.4, 0.5)

  # Parse to cograph
  parsed <- parse_igraph(g_orig)

  # Verify structure preserved
  expect_equal(nrow(parsed$nodes), 5)
  expect_equal(nrow(parsed$edges), 5)
  expect_equal(parsed$nodes$label, LETTERS[1:5])
  expect_equal(parsed$weights, c(0.1, 0.2, 0.3, 0.4, 0.5))
})

# =============================================================================
# Test: Edge Cases and Special Graphs
# =============================================================================

test_that("parse_igraph handles complete graph", {
  skip_if_not_installed("igraph")

  g <- igraph::make_full_graph(5)
  result <- parse_igraph(g)

  expect_equal(nrow(result$nodes), 5)
  expect_equal(nrow(result$edges), 10)  # 5 choose 2 = 10 edges
})

test_that("parse_igraph handles star graph", {
  skip_if_not_installed("igraph")

  g <- igraph::make_star(5, mode = "undirected")
  result <- parse_igraph(g)

  expect_equal(nrow(result$nodes), 5)
  expect_equal(nrow(result$edges), 4)  # n-1 edges
})

test_that("parse_igraph handles single node graph", {
  skip_if_not_installed("igraph")

  g <- igraph::make_empty_graph(1)
  result <- parse_igraph(g)

  expect_equal(nrow(result$nodes), 1)
  expect_equal(nrow(result$edges), 0)
  expect_equal(result$nodes$label, "1")
})

test_that("parse_igraph handles graph with self-loops", {
  skip_if_not_installed("igraph")

  g <- igraph::make_empty_graph(3)
  g <- igraph::add_edges(g, c(1, 1, 1, 2, 2, 3))  # self-loop on 1
  result <- parse_igraph(g)

  expect_equal(nrow(result$nodes), 3)
  expect_equal(nrow(result$edges), 3)
})

test_that("network_to_igraph handles network without labels", {
  skip_if_not_installed("igraph")

  # Create network where nodes have no label column
  nodes_df <- data.frame(id = 1:3, x = NA_real_, y = NA_real_)
  # Explicitly add NULL-ish label behavior
  nodes_df$label <- NA_character_

  net <- list(
    nodes = nodes_df,
    edges = data.frame(from = c(1L, 2L), to = c(2L, 3L), weight = c(1, 1)),
    directed = FALSE
  )
  class(net) <- c("cograph_network", "list")

  # Should handle gracefully
  g <- network_to_igraph(net)
  expect_true(inherits(g, "igraph"))
  expect_equal(igraph::vcount(g), 3)
})

# =============================================================================
# Test: Integration with CographNetwork (R6) Class
# =============================================================================

test_that("apply_igraph_layout works with CographNetwork R6 object", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- CographNetwork$new(mat)

  coords <- apply_igraph_layout(net, igraph::layout_in_circle)

  expect_equal(nrow(coords), 3)
  expect_true(all(c("x", "y") %in% names(coords)))
})

test_that("apply_igraph_layout_by_name works with CographNetwork R6 object", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), nrow = 3)
  net <- CographNetwork$new(mat)

  coords <- apply_igraph_layout_by_name(net, "kk", seed = 42)

  expect_equal(nrow(coords), 3)
})

# =============================================================================
# Test: Coordinate Normalization Edge Cases
# =============================================================================

test_that("normalize_coords handles very small range", {
  coords <- matrix(c(0.001, 0.002, 0.003, 0.001, 0.002, 0.003), ncol = 2)
  result <- normalize_coords(coords)

  expect_true(all(result >= 0.1))
  expect_true(all(result <= 0.9))
})

test_that("normalize_coords handles very large range", {
  coords <- matrix(c(-1e6, 0, 1e6, -1e6, 0, 1e6), ncol = 2)
  result <- normalize_coords(coords)

  expect_true(all(result >= 0.1 - 1e-10))
  expect_true(all(result <= 0.9 + 1e-10))
})

test_that("normalize_coords handles NA values gracefully", {
  # NA values should be handled by na.rm = TRUE in range()
  coords <- matrix(c(0, NA, 100, 0, 50, 100), ncol = 2)
  result <- normalize_coords(coords)

  # Non-NA values should be normalized
  expect_true(!is.na(result[1, 1]))
  expect_true(!is.na(result[3, 1]))
})

# =============================================================================
# Test: Error Messages Quality
# =============================================================================

test_that("error message for unknown layout lists available options", {
  skip_if_not_installed("igraph")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  net <- as_cograph(mat)

  err <- tryCatch(
    apply_igraph_layout_by_name(net, "nonexistent"),
    error = function(e) conditionMessage(e)
  )

  expect_true(grepl("Unknown igraph layout", err))
  expect_true(grepl("Available", err))
})

test_that("parse_igraph error message is clear for invalid input", {
  skip_if_not_installed("igraph")

  err <- tryCatch(
    parse_igraph(data.frame(a = 1:3)),
    error = function(e) conditionMessage(e)
  )

  expect_true(grepl("igraph object", err))
})

Try the cograph package in your browser

Any scripts or data that you put into this service are public.

cograph documentation built on April 1, 2026, 1:07 a.m.