tests/testthat/test-coverage-motifs-41.R

# Test coverage for R/motifs.R - Additional coverage tests (41)
# Targets uncovered code paths, edge cases, and functions not in 40/43

# =============================================================================
# TEST SETUP AND HELPER FUNCTIONS
# =============================================================================

# Create test matrices for motif analysis
skip_on_cran()

create_directed_matrix <- function(n = 5, seed = 42) {
  set.seed(seed)
  mat <- matrix(sample(0:1, n * n, replace = TRUE, prob = c(0.6, 0.4)), n, n)
  diag(mat) <- 0
  rownames(mat) <- colnames(mat) <- LETTERS[1:n]
  mat
}

create_undirected_matrix <- function(n = 5, seed = 42) {
  set.seed(seed)
  mat <- matrix(0, n, n)
  upper_idx <- which(upper.tri(mat))
  selected <- sample(upper_idx, length(upper_idx) %/% 2)
  mat[selected] <- 1
  mat <- mat + t(mat)
  rownames(mat) <- colnames(mat) <- LETTERS[1:n]
  mat
}

create_weighted_matrix <- function(n = 5, seed = 42) {
  set.seed(seed)
  mat <- matrix(sample(0:10, n * n, replace = TRUE), n, n)
  diag(mat) <- 0
  rownames(mat) <- colnames(mat) <- LETTERS[1:n]
  mat
}

# =============================================================================
# GET_EDGE_LIST FUNCTION TESTS
# =============================================================================

test_that("get_edge_list errors on non-tna input", {
  mat <- create_directed_matrix(5)
  expect_error(get_edge_list(mat), "tna object")
  expect_error(get_edge_list("invalid"), "tna object")
  expect_error(get_edge_list(list(a = 1)), "tna object")
})

test_that("get_edge_list works with tna object", {
  skip_if_not_installed("tna")

  # Create a simple tna model for testing
  set.seed(123)
  test_data <- data.frame(
    V1 = sample(LETTERS[1:3], 50, replace = TRUE),
    V2 = sample(LETTERS[1:3], 50, replace = TRUE),
    V3 = sample(LETTERS[1:3], 50, replace = TRUE),
    V4 = sample(LETTERS[1:3], 50, replace = TRUE)
  )

  mod <- tna::tna(test_data)

  # Test with by_individual = TRUE
  edges <- get_edge_list(mod, by_individual = TRUE)
  expect_true(is.data.frame(edges))
  expect_true(all(c("id", "from", "to", "count") %in% names(edges)))
  expect_true(all(edges$count >= 0))

  # Test with by_individual = FALSE
  agg_edges <- get_edge_list(mod, by_individual = FALSE)
  expect_true(is.data.frame(agg_edges))
  expect_true(all(c("from", "to", "count") %in% names(agg_edges)))
  expect_false("id" %in% names(agg_edges))

  # Test drop_zeros = FALSE
  edges_with_zeros <- get_edge_list(mod, by_individual = TRUE, drop_zeros = FALSE)
  expect_true(is.data.frame(edges_with_zeros))

  agg_with_zeros <- get_edge_list(mod, by_individual = FALSE, drop_zeros = FALSE)
  expect_true(is.data.frame(agg_with_zeros))
})

# =============================================================================
# EXTRACT_MOTIFS WITH TNA OBJECT TESTS
# =============================================================================

test_that("extract_motifs works with tna object", {
  skip_if_not_installed("tna")
  skip_if_not_installed("igraph")

  set.seed(456)
  test_data <- data.frame(matrix(
    sample(c("A", "B", "C", "D"), 200, replace = TRUE),
    nrow = 50, ncol = 4
  ))

  mod <- tna::tna(test_data)
  result <- extract_motifs(mod, pattern = "all", min_transitions = 0)

  expect_true(is.null(result) || inherits(result, "cograph_motif_analysis"))

  if (!is.null(result)) {
    expect_true("results" %in% names(result))
    expect_true("type_summary" %in% names(result))
    expect_equal(result$params$level, "individual")
  }
})

test_that("extract_motifs with tna uses correct level", {
  skip_if_not_installed("tna")
  skip_if_not_installed("igraph")

  set.seed(789)
  test_data <- data.frame(matrix(
    sample(c("X", "Y", "Z"), 150, replace = TRUE),
    nrow = 30, ncol = 5
  ))

  mod <- tna::tna(test_data)

  # Default level for tna should be individual
  result_default <- extract_motifs(mod, pattern = "all", min_transitions = 0)
  if (!is.null(result_default)) {
    expect_equal(result_default$params$level, "individual")
  }

  # Explicit aggregate level
  result_agg <- extract_motifs(mod, level = "aggregate", pattern = "all", min_transitions = 0)
  if (!is.null(result_agg)) {
    expect_equal(result_agg$params$level, "aggregate")
  }
})

# =============================================================================
# .MOTIF_CENSUS_UNDIRECTED ADDITIONAL TESTS
# =============================================================================

test_that("motif_census undirected handles various methods", {
  skip_if_not_installed("igraph")

  # Create a symmetric matrix
  mat <- matrix(c(
    0, 1, 1, 0, 1,
    1, 0, 1, 1, 0,
    1, 1, 0, 1, 1,
    0, 1, 1, 0, 1,
    1, 0, 1, 1, 0
  ), 5, 5)
  rownames(mat) <- colnames(mat) <- LETTERS[1:5]

  # Configuration method
  result_config <- motif_census(mat, n_random = 5, method = "configuration", seed = 42)
  expect_s3_class(result_config, "cograph_motifs")
  expect_false(result_config$directed)

  # GNM method
  result_gnm <- motif_census(mat, n_random = 5, method = "gnm", seed = 42)
  expect_s3_class(result_gnm, "cograph_motifs")
  expect_equal(result_gnm$method, "gnm")
})

# =============================================================================
# .GENERATE_RANDOM_GRAPH ADDITIONAL TESTS
# =============================================================================

test_that(".generate_random_graph handles undirected with configuration", {
  skip_if_not_installed("igraph")

  generate_random <- cograph:::.generate_random_graph

  # Undirected graph
  mat <- create_undirected_matrix(6, seed = 111)
  g <- igraph::graph_from_adjacency_matrix(mat, mode = "undirected")

  g_rand <- generate_random(g, "configuration")
  expect_true(igraph::is_igraph(g_rand))
  expect_false(igraph::is_directed(g_rand))

  # Verify degree sequence is preserved (approximately)
  orig_deg <- sort(igraph::degree(g))
  rand_deg <- sort(igraph::degree(g_rand))
  expect_equal(length(orig_deg), length(rand_deg))
})

test_that(".generate_random_graph handles directed with gnm", {
  skip_if_not_installed("igraph")

  generate_random <- cograph:::.generate_random_graph

  mat <- create_directed_matrix(6, seed = 222)
  g <- igraph::graph_from_adjacency_matrix(mat, mode = "directed")

  g_rand <- generate_random(g, "gnm")
  expect_true(igraph::is_igraph(g_rand))
  expect_true(igraph::is_directed(g_rand))

  # Edge count should be approximately preserved
  expect_equal(igraph::vcount(g_rand), igraph::vcount(g))
})

# =============================================================================
# PLOT.COGRAPH_MOTIFS NETWORK TYPE EDGE CASES
# =============================================================================

test_that("plot.cograph_motifs network type renders all standard triads", {
  skip_if_not_installed("igraph")
  skip_if_not_installed("ggplot2")

  # Create a dense matrix that should have multiple triad types
  mat <- create_directed_matrix(8, seed = 333)

  result <- motif_census(mat, n_random = 10, seed = 42)

  with_temp_png({
    p <- plot(result, type = "network", show_nonsig = TRUE)
  }, width = 600, height = 600)

  # Should either return NULL or handle gracefully
  expect_true(is.null(p) || is.null(p))
})

test_that("plot.cograph_motifs heatmap handles extreme z-scores", {
  skip_if_not_installed("igraph")
  skip_if_not_installed("ggplot2")

  mat <- create_directed_matrix(8, seed = 444)
  result <- motif_census(mat, n_random = 50, seed = 42)

  with_temp_png({
    p <- plot(result, type = "heatmap", show_nonsig = TRUE)
  }, width = 400, height = 400)

  expect_true(is.null(p) || inherits(p, "gg"))
})

# =============================================================================
# EXTRACT_TRIADS ADDITIONAL EDGE CASES
# =============================================================================

test_that("extract_triads handles directed parameter explicitly", {
  skip_if_not_installed("igraph")

  mat <- create_weighted_matrix(5, seed = 555)

  # Explicit directed = TRUE
  result_dir <- extract_triads(mat, directed = TRUE, min_total = 0)
  expect_true(is.data.frame(result_dir))

  # Explicit directed = FALSE
  result_undir <- extract_triads(mat, directed = FALSE, min_total = 0)
  expect_true(is.data.frame(result_undir))
})

test_that("extract_triads handles combined type and involving filters", {
  skip_if_not_installed("igraph")

  mat <- create_weighted_matrix(6, seed = 666)

  result <- extract_triads(mat, type = c("030T", "030C"), involving = "A", min_total = 0)

  expect_true(is.data.frame(result))
  if (nrow(result) > 0) {
    expect_true(all(result$type %in% c("030T", "030C")))
    has_A <- apply(result[, c("A", "B", "C")], 1, function(x) "A" %in% x)
    expect_true(all(has_A))
  }
})

# =============================================================================
# EXTRACT_MOTIFS SIGNIFICANCE TESTING EDGE CASES
# =============================================================================

test_that("extract_motifs significance test handles edge cases", {
  skip_if_not_installed("igraph")

  mat <- create_weighted_matrix(5, seed = 777)

  # Small n_perm
  result <- extract_motifs(mat, pattern = "all", min_transitions = 0,
                           significance = TRUE, n_perm = 3, seed = 42)

  if (!is.null(result) && nrow(result$results) > 0) {
    expect_true("z" %in% names(result$results))
    expect_true("p" %in% names(result$results))
    expect_true("expected" %in% names(result$results))
    expect_true("sig" %in% names(result$results))
  }
})

test_that("extract_motifs handles by_type sorting", {
  skip_if_not_installed("igraph")

  mat <- create_weighted_matrix(6, seed = 888)

  result <- extract_motifs(mat, pattern = "all", by_type = TRUE, min_transitions = 0)

  if (!is.null(result) && nrow(result$results) > 1) {
    # Check that results are grouped by type
    types <- result$results$type
    # Runs should be somewhat grouped
    expect_true(length(types) > 0)
  }
})

# =============================================================================
# PLOT.COGRAPH_MOTIF_ANALYSIS ADDITIONAL EDGE CASES
# =============================================================================

test_that("plot.cograph_motif_analysis types plot handles empty type_summary", {
  skip_if_not_installed("igraph")
  skip_if_not_installed("ggplot2")

  # Create mock result with empty type_summary
  mock_result <- list(
    results = data.frame(triad = "A - B - C", type = "030T",
                         observed = 1, stringsAsFactors = FALSE),
    type_summary = table(character(0)),  # Empty
    params = list(
      level = "aggregate",
      edge_method = "any",
      pattern = "all",
      significance = FALSE
    )
  )
  class(mock_result) <- "cograph_motif_analysis"

  # Should handle gracefully without error
  result <- with_temp_png({
    tryCatch(
      plot(mock_result, type = "types"),
      error = function(e) "error_caught"
    )
  })
  # Expect either successful plot or graceful error handling

  expect_true(is.null(result) || inherits(result, "gg") || result == "error_caught")
})

test_that("plot.cograph_motif_analysis triads handles various spacing values", {
  skip_if_not_installed("igraph")
  skip_if_not_installed("ggplot2")

  mat <- create_weighted_matrix(5, seed = 999)
  result <- extract_motifs(mat, pattern = "all", min_transitions = 0)

  # Either result is NULL (acceptable) or we can plot
  expect_true(is.null(result) || inherits(result, "cograph_motif_analysis"))

  if (!is.null(result) && nrow(result$results) > 0) {
    # Test different spacing values
    with_temp_png({
      plot(result, type = "triads", n = 4, spacing = 0.5)
    }, width = 400, height = 400)

    with_temp_png({
      plot(result, type = "triads", n = 4, spacing = 2.0)
    }, width = 400, height = 400)
  }
})

test_that("plot.cograph_motif_analysis triads handles n > nrow(results)", {
  skip_if_not_installed("igraph")
  skip_if_not_installed("ggplot2")

  mat <- create_weighted_matrix(4, seed = 111)
  result <- extract_motifs(mat, pattern = "triangle", min_transitions = 0)

  # Either result is NULL (acceptable) or we can plot
  expect_true(is.null(result) || inherits(result, "cograph_motif_analysis"))

  if (!is.null(result) && nrow(result$results) > 0) {
    with_temp_png({
      # Request more triads than available
      plot(result, type = "triads", n = 100)
    }, width = 400, height = 400)
  }
})

# =============================================================================
# .PLOT_MOTIF_PATTERNS ADDITIONAL TESTS
# =============================================================================

test_that(".plot_motif_patterns handles various type counts", {
  skip_if_not_installed("igraph")
  skip_if_not_installed("ggplot2")

  mat <- create_weighted_matrix(6, seed = 222)
  result <- extract_motifs(mat, pattern = "all", min_transitions = 0)

  # Either result is NULL (acceptable) or we can plot
  expect_true(is.null(result) || inherits(result, "cograph_motif_analysis"))

  if (!is.null(result) && length(result$type_summary) > 0) {
    # Test with different n values
    with_temp_png({
      plot(result, type = "patterns", n = 2)
    }, width = 400, height = 400)

    with_temp_png({
      plot(result, type = "patterns", n = 8)
    }, width = 600, height = 600)
  }
})

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.