tests/testthat/test-smap.R

test_that("smap functions preserve names in output", {
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)
  names(structures) <- c("A", "B", "C")

  # smap_int should preserve names
  result <- smap_int(structures, igraph::vcount)
  expect_equal(names(result), c("A", "B", "C"))

  # smap_chr should preserve names
  result_chr <- smap_chr(structures, ~ .x$anomer)
  expect_equal(names(result_chr), c("A", "B", "C"))

  # smap should preserve names in list
  result_list <- smap(structures, ~ igraph::vcount(.x))
  expect_equal(names(result_list), c("A", "B", "C"))
})

test_that("smap functions work with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test smap_int with regular function
  result <- smap_int(structures, igraph::vcount)
  expect_equal(length(result), 3)
  expect_type(result, "integer")

  # Test smap_chr with regular function
  result_chr <- smap_chr(structures, function(g) g$anomer)
  expect_equal(length(result_chr), 3)
  expect_type(result_chr, "character")

  # Test smap_lgl with regular function
  result_lgl <- smap_lgl(structures, function(g) igraph::vcount(g) > 5)
  expect_equal(length(result_lgl), 3)
  expect_type(result_lgl, "logical")
})

test_that("smap functions work with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test smap_int with purrr lambda
  result_lambda <- smap_int(structures, ~ igraph::vcount(.x))
  result_regular <- smap_int(structures, igraph::vcount)
  expect_equal(result_lambda, result_regular)

  # Test smap_chr with purrr lambda
  result_chr_lambda <- smap_chr(structures, ~ .x$anomer)
  result_chr_regular <- smap_chr(structures, function(g) g$anomer)
  expect_equal(result_chr_lambda, result_chr_regular)

  # Test smap_lgl with purrr lambda
  result_lgl_lambda <- smap_lgl(structures, ~ igraph::vcount(.x) > 5)
  result_lgl_regular <- smap_lgl(structures, function(g) igraph::vcount(g) > 5)
  expect_equal(result_lgl_lambda, result_lgl_regular)
})

test_that("smap_unique works with purrr-style lambda functions", {
  # Create test structures with duplicates
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1, core1)

  # Test with regular function
  result_regular <- smap_unique(structures, igraph::vcount)
  expect_equal(length(result_regular), 1) # Only one unique structure

  # Test with purrr lambda
  result_lambda <- smap_unique(structures, ~ igraph::vcount(.x))
  expect_equal(result_lambda, result_regular)
})

test_that("smap_structure works with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1)

  # Function that adds vertex names if not present
  add_names_regular <- function(g) {
    if (!("name" %in% igraph::vertex_attr_names(g))) {
      igraph::set_vertex_attr(
        g,
        "name",
        value = paste0("v", seq_len(igraph::vcount(g)))
      )
    } else {
      g
    }
  }

  # Test with regular function
  result_regular <- smap_structure(structures, add_names_regular)

  # Test with purrr lambda
  result_lambda <- smap_structure(
    structures,
    ~ {
      if (!("name" %in% igraph::vertex_attr_names(.x))) {
        igraph::set_vertex_attr(
          .x,
          "name",
          value = paste0("v", seq_len(igraph::vcount(.x)))
        )
      } else {
        .x
      }
    }
  )

  # Both results should be equivalent
  expect_s3_class(result_lambda, "glyrepr_structure")
  expect_equal(length(result_lambda), length(result_regular))
})

test_that("get_anomer function works correctly", {
  # Test the specific case from the user's example
  x <- n_glycan_core()
  result <- get_anomer(x)
  expect_type(result, "character")
  expect_equal(length(result), 1)
  expect_equal(result, "b1")

  # Test that it's equivalent to purrr lambda style
  result_lambda <- smap_chr(x, ~ .x$anomer)
  expect_equal(result, result_lambda)
})

test_that("ssome works with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1() # smaller structure
  core2 <- n_glycan_core() # larger structure
  structures <- c(core1, core2, core1)

  # Test if some structures have more than 5 vertices
  result <- ssome(structures, function(g) igraph::vcount(g) > 5)
  expect_type(result, "logical")
  expect_equal(length(result), 1)
  expect_true(is.logical(result))

  # Test if some structures have more than 20 vertices (should be FALSE)
  result_false <- ssome(structures, function(g) igraph::vcount(g) > 20)
  expect_false(result_false)

  # Test if some structures have at least 1 vertex (should be TRUE)
  result_true <- ssome(structures, function(g) igraph::vcount(g) >= 1)
  expect_true(result_true)
})

test_that("ssome works with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test with purrr lambda
  result_lambda <- ssome(structures, ~ igraph::vcount(.x) > 5)
  result_regular <- ssome(structures, function(g) igraph::vcount(g) > 5)
  expect_equal(result_lambda, result_regular)

  # Test with another lambda
  result_lambda2 <- ssome(structures, ~ igraph::vcount(.x) >= 1)
  expect_true(result_lambda2)
})

test_that("severy works with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test if all structures have at least 1 vertex (should be TRUE)
  result_true <- severy(structures, function(g) igraph::vcount(g) >= 1)
  expect_true(result_true)

  # Test if all structures have more than 10 vertices (should be FALSE)
  result_false <- severy(structures, function(g) igraph::vcount(g) > 10)
  expect_false(result_false)

  # Test if all structures have at least 3 vertices
  result <- severy(structures, function(g) igraph::vcount(g) >= 3)
  expect_type(result, "logical")
  expect_equal(length(result), 1)
})

test_that("severy works with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test with purrr lambda
  result_lambda <- severy(structures, ~ igraph::vcount(.x) >= 1)
  result_regular <- severy(structures, function(g) igraph::vcount(g) >= 1)
  expect_equal(result_lambda, result_regular)

  # Test with another lambda
  result_lambda2 <- severy(structures, ~ igraph::vcount(.x) >= 3)
  expect_type(result_lambda2, "logical")
})

test_that("snone works with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test if no structures have more than 50 vertices (should be TRUE)
  result_true <- snone(structures, function(g) igraph::vcount(g) > 50)
  expect_true(result_true)

  # Test if no structures have at least 1 vertex (should be FALSE)
  result_false <- snone(structures, function(g) igraph::vcount(g) >= 1)
  expect_false(result_false)

  # Test return type
  result <- snone(structures, function(g) igraph::vcount(g) > 20)
  expect_type(result, "logical")
  expect_equal(length(result), 1)
})

test_that("snone works with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test with purrr lambda
  result_lambda <- snone(structures, ~ igraph::vcount(.x) > 50)
  result_regular <- snone(structures, function(g) igraph::vcount(g) > 50)
  expect_equal(result_lambda, result_regular)

  # Test with another lambda
  result_lambda2 <- snone(structures, ~ igraph::vcount(.x) >= 1)
  expect_false(result_lambda2)
})

test_that("structure predicate functions work with duplicate structures efficiently", {
  # Create structures with many duplicates to test efficiency
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1, core1, core1, core1) # same structure 5 times

  # These should only evaluate the predicate once for the unique structure
  result_some <- ssome(structures, function(g) igraph::vcount(g) > 3)
  result_every <- severy(structures, function(g) igraph::vcount(g) > 3)
  result_none <- snone(structures, function(g) igraph::vcount(g) > 50)

  expect_type(result_some, "logical")
  expect_type(result_every, "logical")
  expect_type(result_none, "logical")

  expect_equal(length(result_some), 1)
  expect_equal(length(result_every), 1)
  expect_equal(length(result_none), 1)
})

test_that("structure predicate functions handle edge cases", {
  # Test with single structure
  single <- o_glycan_core_1()
  expect_type(ssome(single, ~ igraph::vcount(.x) > 0), "logical")
  expect_type(severy(single, ~ igraph::vcount(.x) > 0), "logical")
  expect_type(snone(single, ~ igraph::vcount(.x) > 50), "logical")
})

# Tests for smap2 functions -----------------------------------------

test_that("smap2 functions work with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)
  weights <- c(1.0, 2.0, 1.5)

  # Test smap2_dbl with regular function
  result <- smap2_dbl(structures, weights, function(g, w) igraph::vcount(g) * w)
  expect_equal(length(result), 3)
  expect_type(result, "double")

  # Test smap2_int with regular function
  n_vertices <- c(1, 2, 1)
  result_int <- smap2_int(structures, n_vertices, function(g, n) {
    igraph::vcount(g) + n
  })
  expect_equal(length(result_int), 3)
  expect_type(result_int, "integer")

  # Test smap2_lgl with regular function
  thresholds <- c(5, 6, 5)
  result_lgl <- smap2_lgl(structures, thresholds, function(g, t) {
    igraph::vcount(g) > t
  })
  expect_equal(length(result_lgl), 3)
  expect_type(result_lgl, "logical")
})

test_that("smap2 functions work with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)
  weights <- c(1.0, 2.0, 1.5)

  # Test smap2_dbl with purrr lambda
  result_lambda <- smap2_dbl(structures, weights, ~ igraph::vcount(.x) * .y)
  result_regular <- smap2_dbl(structures, weights, function(g, w) {
    igraph::vcount(g) * w
  })
  expect_equal(result_lambda, result_regular)

  # Test smap2_lgl with purrr lambda
  thresholds <- c(5, 6, 5)
  result_lgl_lambda <- smap2_lgl(
    structures,
    thresholds,
    ~ igraph::vcount(.x) > .y
  )
  result_lgl_regular <- smap2_lgl(structures, thresholds, function(g, t) {
    igraph::vcount(g) > t
  })
  expect_equal(result_lgl_lambda, result_lgl_regular)
})

test_that("smap2 functions work with recycling", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test with single weight (should be recycled)
  single_weight <- 2.5
  result <- smap2_dbl(structures, single_weight, ~ igraph::vcount(.x) * .y)
  expect_equal(length(result), 3)
  expect_type(result, "double")

  # All results should use the same weight
  expected <- smap_dbl(structures, ~ igraph::vcount(.x) * 2.5)
  expect_equal(result, expected)
})

test_that("smap2_structure works with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2) # different structures

  # Function that adds a graph attribute (doesn't change structure topology)
  add_attribute <- function(g, attr_val) {
    igraph::set_graph_attr(g, "test_attr", attr_val)
  }

  attr_values <- c("value1", "value2")
  result <- smap2_structure(structures, attr_values, add_attribute)

  expect_s3_class(result, "glyrepr_structure")
  expect_equal(length(result), 2)

  # Check that the attributes were added correctly
  result_graphs <- get_structure_graphs(result)
  expect_equal(igraph::graph_attr(result_graphs[[1]], "test_attr"), "value1")
  expect_equal(igraph::graph_attr(result_graphs[[2]], "test_attr"), "value2")
})

test_that("smap2_structure works with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2) # different structures

  # Test with purrr lambda that adds vertex attribute based on second argument
  labels <- c("label1", "label2")
  result <- smap2_structure(
    structures,
    labels,
    ~ {
      igraph::set_graph_attr(.x, "custom_label", .y)
    }
  )

  expect_s3_class(result, "glyrepr_structure")
  expect_equal(length(result), 2)

  # Check that the custom attributes were added
  result_graphs <- get_structure_graphs(result)
  expect_equal(igraph::graph_attr(result_graphs[[1]], "custom_label"), "label1")
  expect_equal(igraph::graph_attr(result_graphs[[2]], "custom_label"), "label2")
})

test_that("smap2 functions handle duplicate structures efficiently", {
  # Create structures with duplicates to test efficiency
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1, core1) # same structure 3 times
  weights <- c(1.0, 2.0, 1.0) # first and third are same

  # This should only compute twice: once for (core1, 1.0) and once for (core1, 2.0)
  result <- smap2_dbl(structures, weights, function(g, w) igraph::vcount(g) * w)

  expect_equal(length(result), 3)
  expect_type(result, "double")

  # First and third results should be equal since they have same structure and weight
  expect_equal(result[1], result[3])
  # Second result should be different
  expect_true(result[2] != result[1])
})

test_that("smap2 functions validate inputs", {
  core1 <- o_glycan_core_1()
  structures <- core1

  # Test that first argument must be glycan_structure
  expect_error(smap2_dbl(c(1, 2, 3), c(1, 2, 3), ~ .x * .y), "glycan_structure")
  expect_error(
    smap2_structure("not_a_structure", c(1), ~.x),
    "glycan_structure"
  )

  # Test that smap2_structure validates return type
  expect_error(
    smap2_structure(structures, 1, ~"not_an_igraph"),
    "igraph object"
  )
})

test_that("smap2 functions handle edge cases", {
  # Test with single structure
  single <- o_glycan_core_1()
  result <- smap2_dbl(single, 3.0, ~ igraph::vcount(.x) * .y)
  expect_equal(length(result), 1)
  expect_type(result, "double")

  # Test with empty structures
  empty <- glycan_structure()
  empty_result <- smap2_dbl(empty, numeric(0), ~ igraph::vcount(.x) * .y)
  expect_equal(length(empty_result), 0)
  expect_type(empty_result, "double")
})

test_that("smap2 basic functionality works", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)
  values <- c("a", "b", "c")

  # Test smap2 (returns list)
  result <- smap2(structures, values, function(g, v) {
    list(vcount = igraph::vcount(g), value = v)
  })

  expect_type(result, "list")
  expect_equal(length(result), 3)
  expect_equal(result[[1]]$value, "a")
  expect_equal(result[[2]]$value, "b")
  expect_equal(result[[3]]$value, "c")
})

test_that("smap2_chr works correctly", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)
  prefixes <- c("prefix1_", "prefix2_", "prefix3_")

  # Test smap2_chr
  result <- smap2_chr(structures, prefixes, function(g, p) {
    paste0(p, igraph::vcount(g))
  })

  expect_type(result, "character")
  expect_equal(length(result), 3)
  expect_true(all(grepl("^prefix[123]_\\d+$", result)))
})

# Tests for spmap functions -----------------------------------------

test_that("spmap functions work with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2)
  weights <- c(1.0, 2.0)
  factors <- c(2, 3)

  # Test spmap_dbl with regular function
  result <- spmap_dbl(list(structures, weights, factors), function(g, w, f) {
    igraph::vcount(g) * w * f
  })
  expect_equal(length(result), 2)
  expect_type(result, "double")

  # Test spmap_int with regular function
  add_values <- c(1, 2)
  result_int <- spmap_int(list(structures, add_values), function(g, a) {
    igraph::vcount(g) + a
  })
  expect_equal(length(result_int), 2)
  expect_type(result_int, "integer")

  # Test spmap_lgl with regular function
  thresholds <- c(5, 6)
  result_lgl <- spmap_lgl(list(structures, thresholds), function(g, t) {
    igraph::vcount(g) > t
  })
  expect_equal(length(result_lgl), 2)
  expect_type(result_lgl, "logical")
})

test_that("spmap functions work with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2)
  weights <- c(1.0, 2.0)
  factors <- c(2, 3)

  # Test spmap_dbl with purrr lambda
  result_lambda <- spmap_dbl(
    list(structures, weights, factors),
    ~ igraph::vcount(..1) * ..2 * ..3
  )
  result_regular <- spmap_dbl(
    list(structures, weights, factors),
    function(g, w, f) igraph::vcount(g) * w * f
  )
  expect_equal(result_lambda, result_regular)

  # Test spmap_chr with purrr lambda
  prefixes <- c("pre1_", "pre2_")
  result_chr_lambda <- spmap_chr(
    list(structures, prefixes),
    ~ paste0(..2, igraph::vcount(..1))
  )
  expect_equal(length(result_chr_lambda), 2)
  expect_type(result_chr_lambda, "character")
})

test_that("spmap functions work with recycling", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test with single weight (should be recycled)
  single_weight <- 2.0
  single_factor <- 3
  result <- spmap_dbl(
    list(structures, single_weight, single_factor),
    ~ igraph::vcount(..1) * ..2 * ..3
  )
  expect_equal(length(result), 3)
  expect_type(result, "double")

  # All results should use the same weight and factor
  expected <- smap_dbl(structures, ~ igraph::vcount(.x) * 2.0 * 3)
  expect_equal(result, expected)
})

test_that("spmap_structure works with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2)

  # Function that adds graph attribute based on multiple arguments
  add_attributes <- function(g, label, value) {
    g <- igraph::set_graph_attr(g, "custom_label", label)
    g <- igraph::set_graph_attr(g, "custom_value", value)
    g
  }

  labels <- c("label1", "label2")
  values <- c(10, 20)
  result <- spmap_structure(list(structures, labels, values), add_attributes)

  expect_s3_class(result, "glyrepr_structure")
  expect_equal(length(result), 2)

  # Check that attributes were added
  graphs <- attr(result, "graphs")
  first_structure <- graphs[[vctrs::vec_data(result)[1]]]
  expect_equal(igraph::graph_attr(first_structure, "custom_label"), "label1")
  expect_equal(igraph::graph_attr(first_structure, "custom_value"), 10)
})

test_that("spmap functions handle duplicate structures efficiently", {
  # Create structures with duplicates to test efficiency
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1, core1)
  weights <- c(1.0, 2.0, 1.0)
  factors <- c(2, 3, 2)

  # This should only compute twice: once for (core1, 1.0, 2) and once for (core1, 2.0, 3)
  result <- spmap_dbl(list(structures, weights, factors), function(g, w, f) {
    igraph::vcount(g) * w * f
  })

  expect_equal(length(result), 3)
  expect_type(result, "double")

  # First and third should be equal (same combination)
  expect_equal(result[1], result[3])
  expect_false(result[1] == result[2]) # Different combination
})

test_that("spmap functions validate inputs", {
  core1 <- o_glycan_core_1()
  structures <- core1

  # Test that .l must be a list
  expect_error(spmap_dbl(structures, ~ igraph::vcount(.x)), "non-empty list")
  expect_error(spmap_dbl(list(), ~ igraph::vcount(.x)), "non-empty list")

  # Test that first element must be glycan_structure
  expect_error(
    spmap_dbl(list(c(1, 2, 3), c(1, 2, 3)), ~ .x * .y),
    "glycan_structure"
  )
  expect_error(
    spmap_structure(list("not_a_structure"), ~.x),
    "glycan_structure"
  )

  # Test that spmap_structure validates return type
  expect_error(
    spmap_structure(list(structures, 1), ~"not_an_igraph"),
    "igraph object"
  )
})

test_that("spmap functions handle edge cases", {
  # Test with single structure
  single <- o_glycan_core_1()
  result <- spmap_dbl(list(single, 3.0, 2), ~ igraph::vcount(..1) * ..2 * ..3)
  expect_equal(length(result), 1)
  expect_type(result, "double")

  # Test with empty structures
  empty <- glycan_structure()
  empty_result <- spmap_dbl(
    list(empty, numeric(0), numeric(0)),
    ~ igraph::vcount(..1) * ..2 * ..3
  )
  expect_equal(length(empty_result), 0)
  expect_type(empty_result, "double")
})

test_that("spmap basic functionality works", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2)
  values1 <- c("a", "b")
  values2 <- c(1, 2)

  # Test spmap (returns list)
  result <- spmap(list(structures, values1, values2), function(g, v1, v2) {
    list(vcount = igraph::vcount(g), value1 = v1, value2 = v2)
  })

  expect_type(result, "list")
  expect_equal(length(result), 2)
  expect_equal(result[[1]]$value1, "a")
  expect_equal(result[[1]]$value2, 1)
  expect_equal(result[[2]]$value1, "b")
  expect_equal(result[[2]]$value2, 2)
})

# Tests for simap functions -----------------------------------------

test_that("simap functions work with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test simap_chr with regular function (using index)
  result <- simap_chr(structures, function(g, i) {
    paste0("Structure_", i, "_vcount_", igraph::vcount(g))
  })
  expect_equal(length(result), 3)
  expect_type(result, "character")
  expect_true(grepl("Structure_1_", result[1]))
  expect_true(grepl("Structure_2_", result[2]))
  expect_true(grepl("Structure_3_", result[3]))

  # Test simap_int with regular function (using index)
  result_int <- simap_int(structures, function(g, i) igraph::vcount(g) + i)
  expect_equal(length(result_int), 3)
  expect_type(result_int, "integer")

  # Test simap_lgl with regular function (using index)
  result_lgl <- simap_lgl(structures, function(g, i) i > 1)
  expect_equal(length(result_lgl), 3)
  expect_type(result_lgl, "logical")
  expect_false(result_lgl[1]) # index 1
  expect_true(result_lgl[2]) # index 2
  expect_true(result_lgl[3]) # index 3
})

test_that("simap functions work with purrr-style lambda functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)

  # Test simap_chr with purrr lambda
  result_lambda <- simap_chr(
    structures,
    ~ paste0("Pos", .y, "_vertices", igraph::vcount(.x))
  )
  result_regular <- simap_chr(structures, function(g, i) {
    paste0("Pos", i, "_vertices", igraph::vcount(g))
  })
  expect_equal(result_lambda, result_regular)

  # Test simap_dbl with purrr lambda
  result_dbl <- simap_dbl(structures, ~ igraph::vcount(.x) * .y)
  expect_equal(length(result_dbl), 3)
  expect_type(result_dbl, "double")
})

test_that("simap_structure works with regular functions", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2)

  # Function that adds index/name as graph attribute
  add_index_attr <- function(g, idx) {
    igraph::set_graph_attr(g, "position", idx)
  }

  result <- simap_structure(structures, add_index_attr)

  expect_s3_class(result, "glyrepr_structure")
  expect_equal(length(result), 2)

  # Check that attributes were added
  graphs <- attr(result, "graphs")
  first_structure <- graphs[[vctrs::vec_data(result)[1]]]
  expect_equal(igraph::graph_attr(first_structure, "position"), 1)
})

test_that("simap functions handle duplicate structures efficiently", {
  # Create structures with duplicates to test efficiency
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1, core1)

  # This should compute three times since indices are different: (core1, 1), (core1, 2), (core1, 3)
  result <- simap_chr(structures, function(g, i) {
    paste0("Structure_", i, "_vcount_", igraph::vcount(g))
  })

  expect_equal(length(result), 3)
  expect_type(result, "character")

  # All should be different because indices are different
  expect_false(result[1] == result[2])
  expect_false(result[1] == result[3])
  expect_false(result[2] == result[3])

  # Test that each result contains the expected structure and index information
  expect_true(grepl("Structure_1_", result[1]))
  expect_true(grepl("Structure_2_", result[2]))
  expect_true(grepl("Structure_3_", result[3]))
})

test_that("simap functions validate inputs", {
  core1 <- o_glycan_core_1()
  structures <- core1

  # Test that first argument must be glycan_structure
  expect_error(simap_chr(c(1, 2, 3), ~ paste(.x, .y)), "glycan_structure")
  expect_error(simap_structure("not_a_structure", ~.x), "glycan_structure")

  # Test that simap_structure validates return type
  expect_error(
    simap_structure(structures, ~"not_an_igraph"),
    "igraph object"
  )
})

test_that("simap functions handle edge cases", {
  # Test with single structure
  single <- o_glycan_core_1()
  result <- simap_chr(
    single,
    ~ paste0("Index", .y, "_vertices", igraph::vcount(.x))
  )
  expect_equal(length(result), 1)
  expect_type(result, "character")
  expect_true(grepl("Index1_", result[1]))

  # Test with empty structures
  empty <- glycan_structure()
  empty_result <- simap_chr(empty, ~ paste0(.y, "_", igraph::vcount(.x)))
  expect_equal(length(empty_result), 0)
  expect_type(empty_result, "character")
})

test_that("simap basic functionality works", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2)

  # Test simap (returns list)
  result <- simap(structures, function(g, i) {
    list(vcount = igraph::vcount(g), index = i)
  })

  expect_type(result, "list")
  expect_equal(length(result), 2)
  expect_equal(result[[1]]$index, 1)
  expect_equal(result[[2]]$index, 2)
  expect_true(is.numeric(result[[1]]$vcount))
  expect_true(is.numeric(result[[2]]$vcount))
})

# Tests for map interface cleanliness --------------------------------------

test_that("smap interfaces do not expose parallel arguments", {
  map_functions <- list(
    smap,
    smap_vec,
    smap_lgl,
    smap_int,
    smap_dbl,
    smap_chr,
    smap_structure,
    smap_unique,
    smap2,
    smap2_vec,
    smap2_lgl,
    smap2_int,
    smap2_dbl,
    smap2_chr,
    smap2_structure,
    spmap,
    spmap_vec,
    spmap_lgl,
    spmap_int,
    spmap_dbl,
    spmap_chr,
    spmap_structure
  )

  purrr::walk(map_functions, function(map_function) {
    expect_false(".parallel" %in% names(formals(map_function)))
  })
})

test_that("smap handles additional arguments correctly", {
  # Create test structures
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2)

  # Function that takes additional arguments
  func_with_args <- function(g, multiplier = 1, offset = 0) {
    igraph::vcount(g) * multiplier + offset
  }

  # Test with additional arguments
  result1 <- smap_int(
    structures,
    func_with_args,
    multiplier = 2,
    offset = 1
  )

  expect_equal(length(result1), 2)
  expect_type(result1, "integer")

  # Verify the calculation is correct by computing expected values
  # Get the actual structures from the glycan_structure object
  graphs <- attr(structures, "graphs")
  codes <- vctrs::vec_data(structures)

  expected1 <- igraph::vcount(graphs[[codes[1]]]) * 2 + 1
  expected2 <- igraph::vcount(graphs[[codes[2]]]) * 2 + 1
  expect_equal(result1[1], expected1)
  expect_equal(result1[2], expected2)
})

test_that("smap_structure correctly updates unique structures count when modifications create duplicates", {
  # Create structures that will become identical after modification
  glycans <- c("Gal(a1-3)GalNAc(a1-", "Gal(a1-4)GalNAc(a1-")
  structures <- as_glycan_structure(glycans)

  # Before modification: should have 2 unique structures
  expect_equal(length(attr(structures, "graphs")), 2)

  # Remove linkages - both structures should become identical
  result <- remove_linkages(structures)

  # After modification: should have only 1 unique structure
  expect_equal(length(attr(result, "graphs")), 1)

  # Both elements should have the same IUPAC code
  expect_equal(as.character(result)[1], as.character(result)[2])
  expect_equal(as.character(result)[1], "Gal(??-?)GalNAc(??-")
})

test_that("smap2_structure correctly updates unique structures count when modifications create duplicates", {
  # Create structures that will become identical after modification
  glycans <- c("Gal(a1-3)GalNAc(a1-", "Gal(a1-4)GalNAc(a1-")
  structures <- as_glycan_structure(glycans)
  values <- c("test1", "test2")

  # Before modification: should have 2 unique structures
  expect_equal(length(attr(structures, "graphs")), 2)

  # Function that removes linkages regardless of second argument
  remove_linkages_func <- function(g, val) {
    igraph::set_edge_attr(g, "linkage", value = "??-?") |>
      igraph::set_graph_attr("anomer", value = "??")
  }

  result <- smap2_structure(structures, values, remove_linkages_func)

  # After modification: should have only 1 unique structure
  expect_equal(length(attr(result, "graphs")), 1)

  # Both elements should have the same IUPAC code
  expect_equal(as.character(result)[1], as.character(result)[2])
  expect_equal(as.character(result)[1], "Gal(??-?)GalNAc(??-")
})

test_that("spmap_structure correctly updates unique structures count when modifications create duplicates", {
  # Create structures that will become identical after modification
  glycans <- c("Gal(a1-3)GalNAc(a1-", "Gal(a1-4)GalNAc(a1-")
  structures <- as_glycan_structure(glycans)
  values1 <- c("test1", "test2")
  values2 <- c(1, 2)

  # Before modification: should have 2 unique structures
  expect_equal(length(attr(structures, "graphs")), 2)

  # Function that removes linkages regardless of other arguments
  remove_linkages_func <- function(g, val1, val2) {
    igraph::set_edge_attr(g, "linkage", value = "??-?") |>
      igraph::set_graph_attr("anomer", value = "??")
  }

  result <- spmap_structure(
    list(structures, values1, values2),
    remove_linkages_func
  )

  # After modification: should have only 1 unique structure
  expect_equal(length(attr(result, "graphs")), 1)

  # Both elements should have the same IUPAC code
  expect_equal(as.character(result)[1], as.character(result)[2])
  expect_equal(as.character(result)[1], "Gal(??-?)GalNAc(??-")
})

test_that("structure mappers recanonicalize floating candidate indices", {
  glycan <- as_glycan_structure(
    "{Neu5Ac(a2-4)|3,4}Fuc(a1-2)[Gal(a1-3)]Man(a1-"
  )
  strip_tree_linkages <- function(graph, ...) {
    graph <- igraph::set_edge_attr(graph, "linkage", value = "??-?")
    igraph::set_graph_attr(graph, "anomer", value = "??")
  }

  results <- list(
    smap = smap_structure(glycan, strip_tree_linkages),
    smap2 = smap2_structure(glycan, 1, strip_tree_linkages),
    spmap = spmap_structure(list(glycan, 1), strip_tree_linkages),
    simap = simap_structure(glycan, strip_tree_linkages)
  )

  purrr::walk(results, function(result) {
    expect_identical(
      as.character(result),
      "{Neu5Ac(a2-4)|2,4}Gal(??-?)[Fuc(??-?)]Man(??-"
    )
    expect_identical(
      structure_floating_parts(result)$parents[[1]],
      c(2L, 4L)
    )
  })
})

test_that("structure mappers preserve cross-component parent relations", {
  glycan <- as_glycan_structure(
    paste0(
      "{?S|1,3}",
      "{Fuc(a1-2)|2,3}",
      "{Man(a1-3)|1,3}",
      "Glc(a1-"
    )
  )
  permute_forest <- function(graph, ...) {
    igraph::V(graph)$source_id <- seq_len(igraph::vcount(graph))
    graph <- igraph::permute(graph, rev(seq_len(igraph::vcount(graph))))
    old_to_new <- match(
      seq_len(igraph::vcount(graph)),
      igraph::V(graph)$source_id
    )
    parts <- purrr::map(graph$floating_parts, function(part) {
      part$root <- as.integer(old_to_new[part$root])
      part$nodes <- sort(as.integer(old_to_new[part$nodes]))
      part$parents <- sort(as.integer(old_to_new[part$parents]))
      part
    })
    substituents <- purrr::map(
      graph$floating_substituents,
      function(substituent) {
        substituent$parents <- sort(as.integer(
          old_to_new[substituent$parents]
        ))
        substituent
      }
    )
    graph <- set_floating_parts_attr(graph, parts)
    graph <- set_floating_substituents_attr(graph, substituents)
    igraph::delete_vertex_attr(graph, "source_id")
  }

  transformed <- smap_structure(glycan, permute_forest)

  expect_identical(as.character(transformed), as.character(glycan))
  expect_true(unname(transformed == glycan))
  expect_identical(
    structure_floating_parts(transformed)$parents,
    list(c(2L, 3L), c(1L, 3L))
  )
  expect_identical(
    structure_floating_substituents(transformed)$parents,
    list(c(1L, 3L))
  )
})

test_that("structure mappers validate floating metadata returned by callbacks", {
  glycan <- as_glycan_structure(
    "{Neu5Ac(a2-6)|2,3}Gal(b1-3)GalNAc(a1-"
  )

  expect_snapshot(
    smap_structure(glycan, delete_floating_parts_attr),
    error = TRUE
  )
})

test_that("structure mappers reuse unchanged validated graphs", {
  glycan <- c(
    first = o_glycan_core_1(),
    duplicate = o_glycan_core_1(),
    missing = glycan_structure(NA)
  )
  original_validator <- validate_glycan_graph
  validation_count <- 0
  testthat::local_mocked_bindings(
    validate_glycan_graph = function(graph) {
      validation_count <<- validation_count + 1
      original_validator(graph)
    }
  )

  unchanged <- list(
    smap = smap_structure(glycan, identity),
    smap2 = smap2_structure(glycan, 1, function(graph, ...) graph),
    spmap = spmap_structure(
      list(glycan, 1),
      function(graph, ...) graph
    ),
    simap = simap_structure(glycan, function(graph, ...) graph)
  )
  purrr::walk(unchanged, function(result) {
    expect_identical(as.character(result), as.character(glycan))
    expect_identical(names(result), names(glycan))
    expect_identical(is.na(result), is.na(glycan))
    expect_length(attr(result, "graphs"), 1)
  })
  expect_equal(validation_count, 0)

  smap_structure(
    glycan[1],
    function(graph) {
      igraph::set_graph_attr(graph, "mapped", value = TRUE)
    }
  )
  expect_equal(validation_count, 1)
})

# Additional regression tests for the smap2 nested list fix
test_that("smap2 handles real glycan structures with nested match results correctly", {
  # Create a realistic glycan structure (simulating glyenzy use case)
  glycan_code <- "GlcNAc(b1-2)Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-"
  structures <- as_glycan_structure(glycan_code)

  # Create nested list simulating enzyme rule match results
  # This is the exact structure that was causing the original bug
  rule_matches <- list(list(c(6, 5, 4, 3, 2, 1)))

  # Test that smap2 processes this correctly
  call_count <- 0
  result <- smap2(structures, rule_matches, function(graph, match_data) {
    call_count <<- call_count + 1

    # Verify the graph is correct
    expect_true(igraph::is_igraph(graph))
    expect_equal(igraph::vcount(graph), 6)

    # Verify the match data structure is preserved
    expect_true(is.list(match_data))
    expect_length(match_data, 1)
    expect_equal(match_data[[1]], c(6, 5, 4, 3, 2, 1))

    return("processed")
  })

  # Should be called exactly once (not 6 times due to expansion bug)
  expect_equal(call_count, 1)
  expect_length(result, 1)
  expect_equal(result[[1]], "processed")
})

test_that("smap2 tibble fix handles multiple glycans with different nested structures", {
  # Create multiple structures
  glycan_codes <- c(
    "GlcNAc(b1-2)Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-",
    "Gal(a1-3)GalNAc(a1-"
  )
  structures <- as_glycan_structure(glycan_codes)

  # Create different nested structures for each glycan
  nested_data <- list(
    list(c(6, 5, 4, 3, 2, 1)), # First glycan: complex match
    list(c(2, 1)) # Second glycan: simple match
  )

  results <- smap2(structures, nested_data, function(graph, match_data) {
    return(list(
      vertex_count = igraph::vcount(graph),
      match_length = length(match_data[[1]])
    ))
  })

  # Verify results correspond correctly to inputs
  expect_length(results, 2)
  expect_equal(results[[1]]$vertex_count, 6)
  expect_equal(results[[1]]$match_length, 6)
  expect_equal(results[[2]]$vertex_count, 2)
  expect_equal(results[[2]]$match_length, 2)
})

test_that("smap2 hash generation works correctly for different list structures", {
  # Test the hash-based key generation with various list structures
  structures <- as_glycan_structure("Gal(a1-3)GalNAc(a1-")[1]

  # Different nested list structures that should produce different keys
  list_variants <- list(
    list(c(1, 2, 3)),
    list(c(3, 2, 1)), # Same elements, different order
    list(c(1, 2, 3, 4)), # Different length
    list(list(a = 1, b = 2)) # Named list
  )

  # Each should be handled correctly without expansion
  for (i in seq_along(list_variants)) {
    result <- smap2(structures, list_variants[i], function(graph, data) {
      expect_true(is.list(data))
      return(paste0("variant_", i))
    })

    expect_length(result, 1)
    expect_equal(result[[1]], paste0("variant_", i))
  }
})

test_that("smap2 refactored code maintains performance with complex inputs", {
  # Performance regression test for the refactored hash generation
  glycan_codes <- c(
    "Gal(a1-3)GalNAc(a1-",
    "GlcNAc(b1-2)Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-",
    "Man(a1-2)Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-"
  )
  structures <- as_glycan_structure(glycan_codes)

  # Create moderately complex nested structures
  complex_nested <- list(
    list(c(1:10)),
    list(list(x = 1:5, y = letters[1:5])),
    list(matrix(1:6, nrow = 2))
  )

  # Should complete without errors and reasonable time
  start_time <- Sys.time()
  result <- smap2(structures, complex_nested, function(graph, data) {
    return("completed")
  })
  end_time <- Sys.time()

  # Basic correctness checks
  expect_length(result, 3)
  expect_true(all(unlist(result) == "completed"))

  # Performance should be reasonable (less than 1 second for this simple case)
  expect_true(as.numeric(end_time - start_time) < 1)
})

test_that("smap2 edge case: empty lists and mixed types", {
  glycan_codes <- c(
    "Gal(a1-3)GalNAc(a1-",
    "GlcNAc(b1-2)Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-"
  )
  structures <- as_glycan_structure(glycan_codes)

  # Test with empty lists and mixed simple/complex types
  mixed_data <- list(
    list(), # Empty list
    "simple_string" # Non-list data
  )

  result <- smap2(structures, mixed_data, function(graph, data) {
    if (is.list(data)) {
      return(paste0("list_", length(data)))
    } else {
      return(paste0("non_list_", data))
    }
  })

  expect_length(result, 2)
  expect_equal(result[[1]], "list_0")
  expect_equal(result[[2]], "non_list_simple_string")
})

# Tests for names preservation -----------------------------------------

test_that("smap_structure preserves names", {
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1)
  names(structures) <- c("X", "Y")

  add_attr <- function(g) {
    igraph::set_graph_attr(g, "test", "value")
  }

  result <- smap_structure(structures, add_attr)
  expect_equal(names(result), c("X", "Y"))
})

test_that("smap2 functions preserve names", {
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)
  names(structures) <- c("A", "B", "C")
  weights <- c(1.0, 2.0, 1.0)

  # smap2_dbl should preserve names
  result <- smap2_dbl(structures, weights, function(g, w) igraph::vcount(g) * w)
  expect_equal(names(result), c("A", "B", "C"))

  # smap2 should preserve names in list
  result_list <- smap2(structures, weights, function(g, w) {
    list(count = igraph::vcount(g), weight = w)
  })
  expect_equal(names(result_list), c("A", "B", "C"))
})

test_that("smap2_structure preserves names", {
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1)
  names(structures) <- c("X", "Y")
  values <- c(1, 2)

  add_attr <- function(g, v) {
    igraph::set_graph_attr(g, "value", v)
  }

  result <- smap2_structure(structures, values, add_attr)
  expect_equal(names(result), c("X", "Y"))
})

# Tests for spmap names preservation -----------------------------------------

test_that("spmap functions preserve names", {
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2)
  names(structures) <- c("A", "B")
  weights <- c(1.0, 2.0)
  factors <- c(2, 3)

  # spmap_dbl should preserve names
  result <- spmap_dbl(list(structures, weights, factors), function(g, w, f) {
    igraph::vcount(g) * w * f
  })
  expect_equal(names(result), c("A", "B"))

  # spmap should preserve names in list
  result_list <- spmap(list(structures, weights, factors), function(g, w, f) {
    list(count = igraph::vcount(g), weight = w)
  })
  expect_equal(names(result_list), c("A", "B"))
})

test_that("spmap_structure preserves names", {
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1)
  names(structures) <- c("X", "Y")
  values1 <- c("a", "b")
  values2 <- c(1, 2)

  add_attrs <- function(g, v1, v2) {
    g <- igraph::set_graph_attr(g, "label", v1)
    igraph::set_graph_attr(g, "num", v2)
  }

  result <- spmap_structure(list(structures, values1, values2), add_attrs)
  expect_equal(names(result), c("X", "Y"))
})

# Tests for simap names preservation -----------------------------------------

test_that("simap functions preserve names", {
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)
  names(structures) <- c("A", "B", "C")

  # simap_chr should preserve names
  result <- simap_chr(structures, function(g, i) paste0("Structure_", i))
  expect_equal(names(result), c("A", "B", "C"))

  # simap should preserve names in list
  result_list <- simap(structures, function(g, i) list(index = i))
  expect_equal(names(result_list), c("A", "B", "C"))
})

test_that("simap_structure preserves names", {
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1)
  names(structures) <- c("X", "Y")

  add_index_attr <- function(g, idx) {
    igraph::set_graph_attr(g, "index", idx)
  }

  result <- simap_structure(structures, add_index_attr)
  expect_equal(names(result), c("X", "Y"))
})

test_that("smap_unique documents behavior with named input", {
  core1 <- o_glycan_core_1()
  structures <- c(core1, core1, core1)
  names(structures) <- c("A", "B", "C") # All same structure, different names

  # smap_unique operates on unique structures only
  result <- smap_unique(structures, igraph::vcount)

  # Result is named by structure hash (IUPAC), not input names
  expect_true(is.null(names(result)) || startsWith(names(result), "Gal"))
})

test_that("get_structure_level preserves names", {
  core1 <- o_glycan_core_1()
  core2 <- n_glycan_core()
  structures <- c(core1, core2, core1)
  names(structures) <- c("A", "B", "C")

  result <- get_structure_level(structures)
  expect_identical(result, c(A = "intact", B = "intact", C = "intact"))
})

test_that("smap_structure can create mixed residue types", {
  structures <- c(
    o_glycan_core_1(),
    n_glycan_core(mono_type = "generic")
  )

  result <- smap_structure(structures, function(graph) {
    replacement <- if (get_mono_type(graph) == "concrete") "Hex" else "Gal"
    igraph::set_vertex_attr(graph, "mono", index = 1, value = replacement)
  })

  expect_identical(get_mono_type(result), c("mixed", "mixed"))
})

# Tests for NA handling in smap functions -----------------------------------

test_that("smap skips NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())
  counts <- smap_int(structs, igraph::vcount)
  expect_equal(length(counts), 3)
  expect_false(is.na(counts[1]))
  expect_true(is.na(counts[2]))
  expect_false(is.na(counts[3]))
})

test_that("smap_structure skips NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())
  result <- smap_structure(structs, identity)
  expect_equal(length(result), 3)
  expect_false(is.na(result[1]))
  expect_true(is.na(result[2]))
  expect_false(is.na(result[3]))
})

test_that("ssome skips NA elements", {
  structs <- c(glycan_structure(NA), o_glycan_core_1())
  expect_true(ssome(structs, function(g) igraph::vcount(g) > 1))
})

test_that("severy skips NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA))
  expect_true(severy(structs, function(g) igraph::vcount(g) > 1))
})

test_that("snone skips NA elements", {
  structs <- glycan_structure(NA, NA)
  expect_true(snone(structs, function(g) igraph::vcount(g) > 100))
})

test_that(".smap_base handles NA elements correctly", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())

  # smap_int should return NA for position 2
  result <- smap_int(structs, igraph::vcount)
  expect_equal(length(result), 3)
  expect_false(is.na(result[1]))
  expect_true(is.na(result[2]))
  expect_false(is.na(result[3]))

  # smap_chr should return NA for position 2
  result_chr <- smap_chr(structs, ~ .x$anomer)
  expect_equal(length(result_chr), 3)
  expect_false(is.na(result_chr[1]))
  expect_true(is.na(result_chr[2]))
  expect_false(is.na(result_chr[3]))

  # smap should return NULL for position 2 (for composition compatibility)
  result_list <- smap(structs, igraph::vcount)
  expect_equal(length(result_list), 3)
  expect_false(is.na(result_list[[1]]))
  expect_true(is.null(result_list[[2]]))
  expect_false(is.na(result_list[[3]]))
})

test_that(".smap_base handles all-NA vector with correct NA types", {
  # All-NA vector
  all_na_structs <- glycan_structure(NA, NA, NA)

  # smap_chr should return NA_character_, not "NA" string
  result_chr <- smap_chr(all_na_structs, ~ .x$anomer)
  expect_equal(length(result_chr), 3)
  expect_type(result_chr, "character")
  expect_true(all(is.na(result_chr)))
  # Verify it's true NA_character_, not the string "NA"
  expect_identical(result_chr[1], NA_character_)

  # smap_int should return NA_integer_
  result_int <- smap_int(all_na_structs, igraph::vcount)
  expect_equal(length(result_int), 3)
  expect_type(result_int, "integer")
  expect_true(all(is.na(result_int)))

  # smap_dbl should return NA_real_
  result_dbl <- smap_dbl(all_na_structs, igraph::vcount)
  expect_equal(length(result_dbl), 3)
  expect_type(result_dbl, "double")
  expect_true(all(is.na(result_dbl)))

  # smap_lgl should return NA (logical)
  result_lgl <- smap_lgl(all_na_structs, ~ igraph::vcount(.x) > 0)
  expect_equal(length(result_lgl), 3)
  expect_type(result_lgl, "logical")
  expect_true(all(is.na(result_lgl)))

  # smap returns list with NULL for NA elements
  result_list <- smap(all_na_structs, igraph::vcount)
  expect_equal(length(result_list), 3)
  expect_true(all(purrr::map_lgl(result_list, is.null)))
})

# Tests for NA handling in smap2 functions -----------------------------------

test_that("smap2 handles NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())

  # smap2_dbl should return NA for position 2
  result <- smap2_dbl(structs, c(1, 2, 3), function(g, n) igraph::vcount(g) + n)
  expect_equal(length(result), 3)
  expect_false(is.na(result[1]))
  expect_true(is.na(result[2]))
  expect_false(is.na(result[3]))

  # smap2 with lambda should work
  result_lambda <- smap2_dbl(structs, c(1, 2, 3), ~ igraph::vcount(.x) + .y)
  expect_equal(result, result_lambda)
})

test_that("smap2 handles NA at different positions", {
  # NA at beginning
  structs1 <- c(glycan_structure(NA), o_glycan_core_1(), n_glycan_core())
  result1 <- smap2_dbl(structs1, c(1, 2, 3), ~ igraph::vcount(.x) + .y)
  expect_true(is.na(result1[1]))
  expect_false(is.na(result1[2]))
  expect_false(is.na(result1[3]))

  # NA at end
  structs3 <- c(o_glycan_core_1(), n_glycan_core(), glycan_structure(NA))
  result3 <- smap2_dbl(structs3, c(1, 2, 3), ~ igraph::vcount(.x) + .y)
  expect_false(is.na(result3[1]))
  expect_false(is.na(result3[2]))
  expect_true(is.na(result3[3]))
})

test_that("smap2_structure handles NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())
  names(structs) <- c("A", "B", "C")
  values <- c(1, 2, 3)

  add_attr <- function(g, v) {
    igraph::set_graph_attr(g, "value", v)
  }

  result <- smap2_structure(structs, values, add_attr)

  expect_equal(length(result), 3)
  expect_equal(names(result), c("A", "B", "C"))

  # Check the graphs directly - there should be 2 unique graphs
  result_graphs <- attr(result, "graphs")
  expect_equal(length(result_graphs), 2)

  # Check that attributes were set correctly on the graphs
  expect_equal(igraph::graph_attr(result_graphs[[1]], "value"), 1)
  expect_equal(igraph::graph_attr(result_graphs[[2]], "value"), 3)

  # Verify result has correct codes (2 actual codes + 1 NA)
  result_codes <- vctrs::vec_data(result)
  expect_equal(sum(is.na(result_codes)), 1)
  expect_equal(sum(!is.na(result_codes)), 2)
})

# Tests for NA handling in spmap functions -----------------------------------

test_that("spmap handles NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())

  # spmap_dbl should return NA for position 2
  result <- spmap_dbl(list(structs, c(1, 2, 3)), ~ igraph::vcount(..1) + ..2)
  expect_equal(length(result), 3)
  expect_false(is.na(result[1]))
  expect_true(is.na(result[2]))
  expect_false(is.na(result[3]))

  # spmap with lambda should work
  result_lambda <- spmap_dbl(list(structs, c(1, 2, 3)), function(g, n) {
    igraph::vcount(g) + n
  })
  expect_equal(result, result_lambda)
})

test_that("spmap_structure handles NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())

  result <- spmap_structure(list(structs, c(1, 2, 3)), function(g, n) g)
  expect_equal(length(result), 3)
  expect_false(is.na(result[1]))
  expect_true(is.na(result[2]))
  expect_false(is.na(result[3]))
})

# Tests for NA handling in simap functions -----------------------------------

test_that("simap handles NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())

  result <- simap_int(structs, function(g, i) igraph::vcount(g) + i)

  expect_equal(length(result), 3)
  expect_false(is.na(result[1]))
  expect_true(is.na(result[2]))
  expect_false(is.na(result[3]))
})

test_that("simap_structure handles NA elements", {
  structs <- c(o_glycan_core_1(), glycan_structure(NA), n_glycan_core())

  result <- simap_structure(structs, function(g, i) g)

  expect_equal(length(result), 3)
  expect_false(is.na(result[1]))
  expect_true(is.na(result[2]))
  expect_false(is.na(result[3]))
})

Try the glyrepr package in your browser

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

glyrepr documentation built on Sept. 22, 2026, 5:09 p.m.