Nothing
# Tests for glycan structure functions
good_glycan_graph <- function() {
graph <- igraph::make_graph(~ 1 - +2, 2 - +3)
igraph::V(graph)$name <- as.character(1:igraph::vcount(graph))
igraph::V(graph)$mono <- c("Glc", "Gal", "Glc")
igraph::V(graph)$sub <- c("", "", "")
igraph::E(graph)$linkage <- c("b1-4", "b1-4")
graph$anomer <- "a1"
graph
}
# Tests for glycan_structure --------------------------------------------------
test_that("glycan_structure works", {
glycan <- glycan_structure(good_glycan_graph())
expect_s3_class(glycan, c("glyrepr_structure"))
})
test_that("glycan_structure is not a character vector", {
glycan <- n_glycan_core()
expect_false(is.character(glycan))
expect_equal(vctrs::vec_data(glycan), structure_to_iupac(glycan))
})
test_that("glycan_structure fails for invalid graphs", {
bad_graph <- igraph::make_graph(~ 1 - +2, 2 - +3, 3 - +1)
expect_error(glycan_structure(bad_graph))
})
test_that("vertex names are added if missing", {
graph <- good_glycan_graph()
graph <- igraph::delete_vertex_attr(graph, "name")
glycan_vec <- glycan_structure(graph)
glycan <- get_structure_graphs(glycan_vec, return_list = FALSE)
expect_true("name" %in% igraph::vertex_attr_names(glycan))
})
# Tests for glycan_structure validation during creation --------------------------
test_that("glycan_structure accepts valid graphs", {
graph <- good_glycan_graph()
expect_no_error(glycan_structure(graph))
})
test_that("glycan_structure rejects invalid graphs", {
# Test undirected graph
graph <- igraph::make_graph(~ 1 - -2)
igraph::V(graph)$mono <- c("Glc", "Gal")
igraph::V(graph)$sub <- c("", "")
igraph::E(graph)$linkage <- "b1-4"
graph$anomer <- "a1"
expect_error(glycan_structure(graph), "directed")
})
# Tests for ensure_name_vertex_attr ------------------------------------------
test_that("ensure_name_vertex_attr adds names when missing", {
graph <- igraph::make_graph(~ 1 - +2)
graph <- igraph::delete_vertex_attr(graph, "name")
result <- ensure_name_vertex_attr(graph)
expect_true("name" %in% igraph::vertex_attr_names(result))
})
test_that("ensure_name_vertex_attr preserves existing names", {
graph <- igraph::make_graph(~ A - +B)
result <- ensure_name_vertex_attr(graph)
expect_equal(igraph::V(result)$name, c("A", "B"))
})
test_that("glycan structure class", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$name <- as.character(1:igraph::vcount(graph))
igraph::V(graph)$mono <- c("GlcNAc", "GlcNAc", "GlcNAc")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-4", "b1-3")
graph$anomer <- "a1"
glycan <- glycan_structure(graph)
expect_s3_class(glycan, c("glyrepr_structure"))
})
test_that("validating undirected graphs", {
graph <- igraph::make_graph(~ 1 - -2)
igraph::V(graph)$mono <- c("GlcNAc", "GlcNAc")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- "b1-4"
graph$anomer <- "a1"
expect_error(glycan_structure(graph), "Glycan structure must be directed")
})
test_that("validating an in tree", {
graph <- igraph::make_tree(3, children = 2, mode = "in")
igraph::V(graph)$mono <- "GlcNAc"
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- "b1-4"
graph$anomer <- "a1"
expect_error(glycan_structure(graph), "Glycan structure must be an out tree")
})
test_that("validating graph without monosaccharide attribute", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- "b1-4"
graph$anomer <- "a1"
expect_error(
glycan_structure(graph),
"Glycan structure must have a vertex attribute 'mono'"
)
})
test_that("validating graph without substituent attribute", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$mono <- "GlcNAc"
igraph::E(graph)$linkage <- "b1-4"
graph$anomer <- "a1"
expect_error(
glycan_structure(graph),
"Glycan structure must have a vertex attribute 'sub'"
)
})
test_that("validating graph with NA in monosaccharide attribute", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$mono <- c("GlcNAc", NA, "GlcNAc")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- "b1-4"
graph$anomer <- "a1"
expect_error(
glycan_structure(graph),
"Glycan structure must have no NA in vertex attribute 'mono'"
)
})
test_that("validating graph with NA in substitude attribute", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$mono <- c("GlcNAc", "GlcNAc", "GlcNAc")
igraph::V(graph)$sub <- c("", NA, "")
igraph::E(graph)$linkage <- "b1-4"
graph$anomer <- "a1"
expect_error(
glycan_structure(graph),
"Glycan structure must have no NA in vertex attribute 'sub'"
)
})
patrick::with_parameters_test_that(
"valid substituents",
{
skip_on_old_win()
graph <- igraph::make_empty_graph(n = 1)
igraph::V(graph)$mono <- "GlcNAc"
igraph::V(graph)$sub <- sub
igraph::E(graph)$linkage <- character(0)
graph$anomer <- "b1"
expect_no_error(glycan_structure(graph))
},
sub = c("6S", "9Ac", "2P", "?S", "4/6S", "3/4/6Ac")
)
test_that("multiple substituents are supported", {
skip_on_old_win()
graph <- igraph::make_empty_graph(n = 1)
igraph::V(graph)$mono <- "Glc"
igraph::V(graph)$sub <- "3Me,4Ac" # Multiple substituents
igraph::E(graph)$linkage <- character(0)
graph$anomer <- "a1"
expect_no_error(glycan_structure(graph))
})
test_that("multiple substituents must be sorted by position", {
skip_on_old_win()
graph <- igraph::make_empty_graph(n = 1)
igraph::V(graph)$mono <- "Glc"
igraph::V(graph)$sub <- "4Ac,3Me" # Wrong order - should be "3Me,4Ac"
igraph::E(graph)$linkage <- character(0)
graph$anomer <- "a1"
expect_error(glycan_structure(graph), "Unknown substituent")
})
test_that("duplicate positions in substituents are not allowed", {
skip_on_old_win()
graph <- igraph::make_empty_graph(n = 1)
igraph::V(graph)$mono <- "Glc"
igraph::V(graph)$sub <- "3Me,3Ac" # Same position (3) with different substituents
igraph::E(graph)$linkage <- character(0)
graph$anomer <- "a1"
expect_error(glycan_structure(graph), "Unknown substituent")
})
patrick::with_parameters_test_that(
"ambiguous substituent positions must be canonical and assignable",
{
skip_on_old_win()
graph <- igraph::make_empty_graph(n = 1)
igraph::V(graph)$mono <- "Glc"
igraph::V(graph)$sub <- sub
igraph::E(graph)$linkage <- character(0)
graph$anomer <- "a1"
expect_error(glycan_structure(graph), "Unknown substituent")
},
sub = c("6/4S", "4/4S", "4Ac,4/6S,6Me")
)
test_that("duplicate unknown substituent positions are allowed", {
skip_on_old_win()
graph <- igraph::make_empty_graph(n = 1)
igraph::V(graph)$mono <- "Glc"
igraph::V(graph)$sub <- "?Me,?Me"
igraph::E(graph)$linkage <- character(0)
graph$anomer <- "a1"
expect_no_error(glycan_structure(graph))
})
test_that("normalize_substituents works correctly", {
expect_equal(normalize_substituents(""), "")
expect_equal(normalize_substituents("6S"), "6S")
expect_equal(normalize_substituents("4Ac,3Me"), "3Me,4Ac")
expect_equal(normalize_substituents("6P,2S,4Ac"), "2S,4Ac,6P")
expect_equal(normalize_substituents("?S,3Me"), "3Me,?S")
})
test_that("validating graph without linkage attribute", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$mono <- c("GlcNAc", "GlcNAc", "GlcNAc")
igraph::V(graph)$sub <- ""
graph$anomer <- "a1"
expect_error(
glycan_structure(graph),
"Glycan structure must have an edge attribute 'linkage'"
)
})
test_that("validating one non-existing monosaccharide", {
graph <- igraph::make_graph(~ 1 - +2, 2 - +3)
igraph::V(graph)$mono <- c("Hex", "Fuc", "Bad")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-4", "b1-4")
graph$anomer <- "a1"
expect_error(glycan_structure(graph))
err <- rlang::catch_cnd(glycan_structure(graph))
# The error is now wrapped by purrr, so check the parent error
expect_true(grepl("Unknown monosaccharide: Bad", err$parent$message))
expect_equal(err$parent$monos, "Bad")
})
test_that("validating two non-existing monosaccharide", {
graph <- igraph::make_graph(~ 1 - +2, 2 - +3)
igraph::V(graph)$mono <- c("Hex", "Bad1", "Bad2")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-4", "b1-4")
graph$anomer <- "a1"
err <- rlang::catch_cnd(glycan_structure(graph))
expect_true(grepl("Unknown monosaccharide: Bad1, Bad2", err$parent$message))
expect_equal(err$parent$monos, c("Bad1", "Bad2"))
})
test_that("validating duplicated non-existing monosaccharide", {
graph <- igraph::make_graph(~ 1 - +2, 2 - +3)
igraph::V(graph)$mono <- c("Hex", "Bad", "Bad")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-4", "b1-4")
graph$anomer <- "a1"
err <- rlang::catch_cnd(glycan_structure(graph))
expect_true(grepl("Unknown monosaccharide: Bad", err$parent$message))
expect_equal(err$parent$monos, "Bad")
})
test_that("validating bad subtituent", {
graph <- igraph::make_graph(~ 1 - +2, 2 - +3)
igraph::V(graph)$mono <- c("Hex", "Hex", "Hex")
igraph::V(graph)$sub <- c("", "6S", "Bad")
igraph::E(graph)$linkage <- c("b1-4", "b1-4")
graph$anomer <- "a1"
expect_error(glycan_structure(graph))
err <- rlang::catch_cnd(glycan_structure(graph))
expect_true(grepl("Unknown substituent: Bad", err$parent$message))
expect_equal(err$parent$subs, "Bad")
})
patrick::with_parameters_test_that(
"validating bad linkage",
{
graph <- igraph::make_graph(~ 1 - +2, 2 - +3)
igraph::V(graph)$mono <- c("Hex", "Fuc", "Hex")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- bad_linkage
graph$anomer <- "a1"
expect_error(glycan_structure(graph))
err <- rlang::catch_cnd(glycan_structure(graph))
},
bad_linkage = c("1-4", "c1-4", "b1", "abc", ""),
.test_name = bad_linkage
)
test_that("validating NA linkages", {
graph <- igraph::make_graph(~ 1 - +2, 2 - +3)
igraph::V(graph)$mono <- c("Hex", "Hex", "Hex")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-4", NA)
graph$anomer <- "a1"
expect_error(glycan_structure(graph))
})
test_that("validating duplicated linkage positions", {
graph <- igraph::make_graph(~ 1 - +2, 1 - +3)
igraph::V(graph)$mono <- c("GalNAc", "Gal", "Neu5Ac")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-3", "a2-3")
graph$anomer <- "a1"
expect_error(glycan_structure(graph), "Duplicated linkage positions")
})
test_that("the same linkage position on different residues is valid", {
graph <- igraph::make_graph(~ 1 - +2, 1 - +3, 2 - +4)
igraph::V(graph)$mono <- c("GalNAc", "Gal", "Neu5Ac", "GlcNAc")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-4", "a2-3", "b1-3")
graph$anomer <- "a1"
expect_no_error(glycan_structure(graph))
})
test_that("duplicated ? linkages are OK", {
graph <- igraph::make_graph(~ 1 - +2, 1 - +3)
igraph::V(graph)$mono <- c("GalNAc", "Gal", "Neu5Ac")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-?", "a2-?")
graph$anomer <- "a1"
expect_no_error(glycan_structure(graph))
})
test_that("duplicated x/y linkages are OK", {
graph <- igraph::make_graph(~ 1 - +2, 1 - +3)
igraph::V(graph)$mono <- c("GalNAc", "Gal", "Neu5Ac")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-3/6", "a2-3/6")
graph$anomer <- "a1"
expect_no_error(glycan_structure(graph))
})
test_that("mixed generic and concrete monosaccharides are valid", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$mono <- c("Hex", "GlcNAc", "Hex")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("b1-3", "b1-4")
graph$anomer <- "a1"
result <- glycan_structure(graph)
expect_s3_class(result, "glyrepr_structure")
expect_identical(get_mono_type(result), "mixed")
})
test_that("missing anomer attr", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$mono <- c("Hex", "Hex", "Hex")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("a1-3", "b1-4")
expect_error(
glycan_structure(graph),
"Glycan structure must have a graph attribute 'anomer'"
)
})
test_that("invalid anomer attr", {
graph <- igraph::make_tree(3, children = 2, mode = "out")
igraph::V(graph)$mono <- c("Hex", "Hex", "Hex")
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- c("a1-3", "b1-4")
graph$anomer <- "a"
expect_error(glycan_structure(graph), "Invalid anomer: a")
})
# Tests for glycan_structure vector functions
# Helper function to create a simple glycan structure
create_simple_glycan_graph <- function(mono_names, linkages, anomer = "?1") {
n_nodes <- length(mono_names)
if (n_nodes == 1) {
graph <- igraph::make_empty_graph(n = 1)
} else {
# Create linear chain: 1-+2-+3-+...
edges <- c()
for (i in 1:(n_nodes - 1)) {
edges <- c(edges, i, i + 1)
}
graph <- igraph::make_graph(edges = edges, directed = TRUE)
}
igraph::V(graph)$name <- as.character(1:igraph::vcount(graph))
igraph::V(graph)$mono <- mono_names
igraph::V(graph)$sub <- ""
igraph::E(graph)$linkage <- linkages
graph$anomer <- anomer
graph
}
# Tests for glycan_structure constructor --------------------------------------------
test_that("glycan_structure creates empty vector by default", {
sv <- glycan_structure()
expect_s3_class(sv, "glyrepr_structure")
expect_equal(length(sv), 0)
expect_length(attr(sv, "graphs"), 0)
})
test_that("glycan_structure preserves interleaved missing values and duplicates", {
graph <- get_structure_graphs(o_glycan_core_1())
result <- glycan_structure(
first = NA,
graph,
gap = NULL,
graph,
NA_character_
)
expect_identical(
as.character(result),
c(
NA_character_,
"Gal(b1-3)GalNAc(a1-",
NA_character_,
"Gal(b1-3)GalNAc(a1-",
NA_character_
)
)
expect_null(names(result))
expect_length(attr(result, "graphs"), 1L)
missing <- glycan_structure(NA, NULL, NA_character_, NA_real_)
expect_identical(as.character(missing), rep(NA_character_, 4L))
expect_identical(attr(missing, "graphs"), list())
})
test_that("glycan_structure works with single glycan structure", {
sv <- o_glycan_core_1()
expect_s3_class(sv, "glyrepr_structure")
expect_equal(length(sv), 1)
expect_length(attr(sv, "graphs"), 1)
expect_equal(structure_to_iupac(sv), "Gal(b1-3)GalNAc(a1-")
})
test_that("glycan_structure works with multiple different glycan structures", {
glycan1 <- o_glycan_core_1()
glycan2 <- n_glycan_core()
sv <- c(glycan1, glycan2)
expect_s3_class(sv, "glyrepr_structure")
expect_equal(length(sv), 2)
expect_length(attr(sv, "graphs"), 2)
})
test_that("glycan_structure removes duplicates based on IUPAC codes", {
glycan1 <- o_glycan_core_1()
glycan2 <- o_glycan_core_1() # Same structure
sv <- c(glycan1, glycan2)
expect_s3_class(sv, "glyrepr_structure")
expect_equal(length(sv), 2) # Original vector has 2 elements
expect_length(attr(sv, "graphs"), 1) # But only 1 unique structure
})
test_that("glycan_structure handles structures with different IUPAC but same graph topology", {
# Create two structures that are topologically the same but have different anomers
graph1 <- create_simple_glycan_graph(c("Glc", "Gal"), "b1-4", "a1")
graph2 <- create_simple_glycan_graph(c("Glc", "Gal"), "b1-4", "b1")
sv <- glycan_structure(graph1, graph2)
expect_equal(length(sv), 2)
expect_length(attr(sv, "graphs"), 2) # Different IUPAC codes
})
test_that("glycan_structure validates input", {
expect_error(glycan_structure("not a graph"), "igraph objects")
expect_error(glycan_structure(list(1, 2)), "igraph objects")
})
test_that("graph construction preserves custom attributes through canonicalization", {
graph <- get_structure_graphs(n_glycan_core())
graph <- igraph::set_vertex_attr(graph, "label", value = letters[1:5])
graph <- igraph::set_edge_attr(graph, "weight", value = seq_len(4))
graph <- igraph::set_graph_attr(graph, "source", value = "example")
graph <- igraph::permute(graph, c(3, 5, 1, 4, 2))
expected <- canonicalize_glycan_graph(validate_glycan_graph(graph))
for (on_failure in c("error", "na")) {
result <- as_glycan_structure(list(graph, graph), on_failure = on_failure)
actual <- get_structure_graphs(result[1])
expect_identical(as.character(result), rep(graph_to_iupac(expected), 2))
expect_length(attr(result, "graphs"), 1L)
expect_identical(
igraph::as_data_frame(actual, "vertices"),
igraph::as_data_frame(expected, "vertices")
)
expect_identical(
igraph::as_data_frame(actual, "edges"),
igraph::as_data_frame(expected, "edges")
)
expect_identical(igraph::graph_attr(actual), igraph::graph_attr(expected))
}
})
# Tests for as_glycan_structure -------------------------------------------------
test_that("as_glycan_structure creates valid glycan_structure object from igraph", {
graph <- create_simple_glycan_graph(c("Glc", "Gal"), "b1-4")
sv <- as_glycan_structure(graph)
expect_s3_class(sv, "glyrepr_structure")
expect_equal(length(sv), 1)
})
test_that("as_glycan_structure works with empty inputs", {
sv <- glycan_structure()
result <- as_glycan_structure(sv)
expect_s3_class(result, "glyrepr_structure")
expect_equal(length(result), 0)
})
test_that("as_glycan_structure can replace invalid graphs with NA", {
valid <- create_simple_glycan_graph("Glc", character())
invalid <- create_simple_glycan_graph("NotAMonosaccharide", character())
graphs <- list(valid = valid, missing = NA, invalid = invalid)
expect_snapshot(
result <- as_glycan_structure(graphs, on_failure = "na")
)
expect_s3_class(result, "glyrepr_structure")
expect_equal(names(result), names(graphs))
expect_equal(
is.na(result),
c(valid = FALSE, missing = TRUE, invalid = TRUE)
)
expect_equal(as.character(result[1]), c(valid = "Glc(?1-"))
expect_length(attr(result, "graphs"), 1)
})
test_that("as_glycan_structure can replace invalid character input with NA", {
iupacs <- c(
valid = "Glc(?1-",
invalid = "not-a-structure",
missing = NA
)
expect_snapshot(
result <- as_glycan_structure(iupacs, on_failure = "na")
)
expect_equal(names(result), names(iupacs))
expect_equal(
is.na(result),
c(valid = FALSE, invalid = TRUE, missing = TRUE)
)
expect_equal(as.character(result[1]), c(valid = "Glc(?1-"))
})
test_that("as_glycan_structure keeps existing missing elements silently", {
valid <- create_simple_glycan_graph("Glc", character())
graphs <- list(valid = valid, missing = NA)
expect_no_warning(
result <- as_glycan_structure(graphs, on_failure = "na")
)
expect_equal(is.na(result), c(valid = FALSE, missing = TRUE))
})
test_that("as_glycan_structure validates graph elements only once", {
original_validator <- validate_glycan_graph
validation_count <- 0
testthat::local_mocked_bindings(
validate_glycan_graph = function(glycan) {
validation_count <<- validation_count + 1
original_validator(glycan)
}
)
valid <- create_simple_glycan_graph("Glc", character())
invalid <- create_simple_glycan_graph("NotAMonosaccharide", character())
suppressWarnings(
as_glycan_structure(list(valid, invalid), on_failure = "na")
)
expect_equal(validation_count, 2)
})
test_that("as_glycan_structure keeps strict failures as the default", {
valid <- create_simple_glycan_graph("Glc", character())
invalid <- create_simple_glycan_graph("NotAMonosaccharide", character())
expect_snapshot(
error = TRUE,
as_glycan_structure(list(valid, invalid))
)
})
test_that("as_glycan_structure accepts mixed vector types with recovery", {
iupacs <- c(concrete = "Glc(?1-", generic = "Hex(??-")
result <- as_glycan_structure(iupacs, on_failure = "na")
expect_identical(
get_mono_type(result),
c(concrete = "concrete", generic = "generic")
)
})
test_that("as_glycan_structure validates on_failure", {
expect_snapshot(
error = TRUE,
as_glycan_structure("Glc(?1-", on_failure = "skip")
)
})
# Tests for is_glycan_structure --------------------------------------------------
test_that("is_glycan_structure correctly identifies glycan_structure objects", {
sv <- glycan_structure()
expect_true(is_glycan_structure(sv))
expect_false(is_glycan_structure(1))
expect_false(is_glycan_structure("test"))
expect_false(is_glycan_structure(list()))
# Note: individual igraph objects should return FALSE for the new vectorized version
graph <- create_simple_glycan_graph(c("Glc", "Gal"), "b1-4")
expect_false(is_glycan_structure(graph))
})
# Tests for format method -----------------------------------------------------
test_that("format.glyrepr_structure displays correct IUPAC sequences", {
glycan1 <- o_glycan_core_1()
glycan2 <- n_glycan_core()
sv <- c(glycan1, glycan2)
formatted <- format(sv)
expect_type(formatted, "character")
expect_equal(
formatted,
c(
"Gal(b1-3)GalNAc(a1- ",
"Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-"
)
)
})
test_that("format.glyrepr_structure handles empty vector", {
sv <- glycan_structure()
formatted <- format(sv)
expect_type(formatted, "character")
expect_length(formatted, 0)
})
test_that("format.glyrepr_structure handles duplicates correctly", {
glycan1 <- o_glycan_core_1()
glycan2 <- o_glycan_core_1()
sv <- c(glycan1, glycan2)
formatted <- format(sv)
expect_length(formatted, 2)
expect_equal(formatted[1], formatted[2]) # Both should show same IUPAC
expect_equal(formatted[1], "Gal(b1-3)GalNAc(a1-")
})
test_that("format.glyrepr_structure includes names with tab separation", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
names(glycans) <- c("A", "B")
expect_snapshot(format(glycans))
})
test_that("format.glyrepr_structure without names works correctly", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
# Ensure no names
glycans <- unname(glycans)
expect_snapshot(format(glycans))
})
test_that("truncation works in tibble", {
sv <- c(n_glycan_core(), n_glycan_core(), n_glycan_core())
tibble <- tibble::tibble(struc = sv, a = 1)
expect_snapshot(print(tibble, width = 30))
})
# Tests for vctrs methods -----------------------------------------------------
test_that("vec_ptype_abbr.glyrepr_structure returns correct abbreviation", {
sv <- glycan_structure()
expect_equal(vctrs::vec_ptype_abbr(sv), "struct")
})
test_that("vec_ptype_full.glyrepr_structure returns correct full type", {
sv <- glycan_structure()
expect_equal(vctrs::vec_ptype_full(sv), "glycan_structure")
})
# Tests for print footer ------------------------------------------------------
test_that("obj_print_footer.glyrepr_structure displays unique count", {
glycan1 <- o_glycan_core_1()
glycan2 <- n_glycan_core()
glycan3 <- o_glycan_core_1() # Duplicate
sv <- c(glycan1, glycan2, glycan3)
output <- capture.output(obj_print_footer.glyrepr_structure(sv))
expect_length(output, 1)
expect_match(output, "# Unique structures: 2")
})
test_that("obj_print_footer.glyrepr_structure handles empty vector", {
sv <- glycan_structure()
output <- capture.output(obj_print_footer.glyrepr_structure(sv))
expect_length(output, 1)
expect_match(output, "# Unique structures: 0")
})
test_that("print.glyrepr_structure supports n", {
structures <- rep(n_glycan_core(), 11)
expect_snapshot_output(print(structures))
expect_snapshot_output(print(structures, n = Inf))
})
# Tests for get_structure_graphs ----------------------------------------------
test_that("get_structure_graphs extracts individual structures correctly", {
glycan1 <- o_glycan_core_1()
glycan2 <- n_glycan_core()
sv <- c(glycan1, glycan2)
# Check that we can retrieve the original structures
extracted_all <- get_structure_graphs(sv)
extracted_single <- get_structure_graphs(sv[1], return_list = FALSE)
expect_type(extracted_all, "list")
expect_length(extracted_all, 2)
expect_s3_class(extracted_all[[1]], "igraph")
expect_s3_class(extracted_all[[2]], "igraph")
expect_s3_class(extracted_single, "igraph")
})
test_that("get_structure_graphs return_list parameter works correctly", {
glycan1 <- o_glycan_core_1()
glycan2 <- n_glycan_core()
sv <- c(glycan1, glycan2)
# Test default behavior (NULL return_list)
# For multiple structures, should return list
result_default_multi <- get_structure_graphs(sv)
expect_type(result_default_multi, "list")
expect_length(result_default_multi, 2)
# For single structure, should return igraph directly
result_default_single <- get_structure_graphs(sv[1])
expect_s3_class(result_default_single, "igraph")
# Test explicit return_list = TRUE
result_list_true <- get_structure_graphs(sv[1], return_list = TRUE)
expect_type(result_list_true, "list")
expect_length(result_list_true, 1)
expect_s3_class(result_list_true[[1]], "igraph")
# Test explicit return_list = FALSE
result_list_false <- get_structure_graphs(sv[1], return_list = FALSE)
expect_s3_class(result_list_false, "igraph")
empty <- as_glycan_structure(character())
expect_identical(get_structure_graphs(empty), list())
expect_identical(get_structure_graphs(empty, return_list = TRUE), list())
})
test_that("get_structure_graphs validates return_list parameter", {
glycan1 <- o_glycan_core_1()
glycan2 <- n_glycan_core()
sv <- c(glycan1, glycan2)
expect_snapshot(
get_structure_graphs(sv, return_list = FALSE),
error = TRUE
)
expect_snapshot(
get_structure_graphs(
as_glycan_structure(character()),
return_list = FALSE
),
error = TRUE
)
})
test_that("as.list returns same-length graph list for structures", {
glycan <- o_glycan_core_1()
sv <- c(glycan, glycan)
iupacs <- structure_to_iupac(sv)
graph_list <- as.list(sv)
expect_type(graph_list, "list")
expect_length(graph_list, 2)
expect_null(names(graph_list))
expect_s3_class(graph_list[[1]], "igraph")
expect_s3_class(graph_list[[2]], "igraph")
expect_false(identical(graph_list[[1]], graph_list[[2]]))
expect_equal(
structure_to_iupac(glycan_structure(graph_list[[1]], graph_list[[2]])),
iupacs
)
})
test_that("as.list preserves structure vector names", {
glycan <- o_glycan_core_1()
sv <- c(first = glycan, second = glycan)
graph_list <- as.list(sv)
expect_equal(names(graph_list), c("first", "second"))
expect_s3_class(graph_list[[1]], "igraph")
expect_s3_class(graph_list[[2]], "igraph")
})
# Integration tests -----------------------------------------------------------
test_that("glycan_structure preserves glycan structures correctly", {
glycan1 <- o_glycan_core_1()
glycan2 <- n_glycan_core()
sv <- c(glycan1, glycan2)
# Check that we can retrieve the original structures
structures <- attr(sv, "graphs")
expect_length(structures, 2)
# Check IUPAC codes are generated correctly
expected_iupacs <- c(structure_to_iupac(glycan1), structure_to_iupac(glycan2))
expect_equal(structure_to_iupac(sv), expected_iupacs)
})
test_that("glycan_structure handles complex branched structures", {
# Create a more complex branched structure
complex_graph <- igraph::make_graph(~ 1 - +2, 1 - +3, 1 - +4)
igraph::V(complex_graph)$mono <- c("Man", "GlcNAc", "Gal", "Fuc")
igraph::V(complex_graph)$sub <- ""
igraph::E(complex_graph)$linkage <- c("b1-4", "a1-3", "a1-6")
complex_graph$anomer <- "a1"
sv <- glycan_structure(complex_graph)
expect_s3_class(sv, "glyrepr_structure")
expect_equal(length(sv), 1)
expect_length(attr(sv, "graphs"), 1)
})
test_that("glycan_structure maintains hash uniqueness property", {
# Create multiple identical structures
graphs <- replicate(
5,
{
create_simple_glycan_graph(c("Glc", "Gal"), "b1-4")
},
simplify = FALSE
)
sv <- as_glycan_structure(graphs)
expect_equal(length(sv), 5) # 5 elements in vector
expect_length(attr(sv, "graphs"), 1) # But only 1 unique structure
})
# Tests for c() function (vec_ptype2 method) ------------------------------------
test_that("c() combines glycan_structure vectors correctly", {
# This test ensures the vec_ptype2 method works correctly
# This was previously failing due to ifelse() with igraph objects
sv1 <- n_glycan_core()
sv2 <- o_glycan_core_1()
# This should not error (previously caused rep.igraph error)
combined <- c(sv1, sv2)
expect_s3_class(combined, "glyrepr_structure")
expect_equal(length(combined), 2)
expect_length(attr(combined, "graphs"), 2) # Two unique structures
# Check that both structures are preserved
expect_equal(
structure_to_iupac(combined),
c(
"Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-",
"Gal(b1-3)GalNAc(a1-"
)
)
})
test_that("c() handles duplicate structures across vectors", {
sv1 <- c(o_glycan_core_1(), n_glycan_core())
sv2 <- o_glycan_core_1() # Duplicate of first structure in sv1
combined <- c(sv1, sv2)
expect_s3_class(combined, "glyrepr_structure")
expect_equal(length(combined), 3) # Total elements
expect_length(attr(combined, "graphs"), 2) # Only 2 unique structures
})
test_that("c() works with empty vectors", {
sv1 <- glycan_structure()
sv2 <- o_glycan_core_1()
combined1 <- c(sv1, sv2)
combined2 <- c(sv2, sv1)
expect_s3_class(combined1, "glyrepr_structure")
expect_s3_class(combined2, "glyrepr_structure")
expect_equal(length(combined1), 1)
expect_equal(length(combined2), 1)
expect_length(attr(combined1, "graphs"), 1)
expect_length(attr(combined2, "graphs"), 1)
})
test_that("c() combines multiple structure vectors efficiently", {
# Test combining multiple vectors with various duplicates
sv1 <- o_glycan_core_1()
sv2 <- n_glycan_core()
sv3 <- c(o_glycan_core_1(), n_glycan_core()) # Contains both
combined <- c(sv1, sv2, sv3)
expect_s3_class(combined, "glyrepr_structure")
expect_equal(length(combined), 4) # 1 + 1 + 2 = 4 total elements
expect_length(attr(combined, "graphs"), 2) # Only 2 unique structures
# Check all elements are present
expect_equal(
structure_to_iupac(combined),
c(
"Gal(b1-3)GalNAc(a1-",
"Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-",
"Gal(b1-3)GalNAc(a1-",
"Man(a1-3)[Man(a1-6)]Man(b1-4)GlcNAc(b1-4)GlcNAc(b1-"
)
)
})
test_that("c() preserves structure integrity across combinations", {
# Use existing example structures
sv1 <- o_glycan_core_1()
sv2 <- n_glycan_core()
combined <- c(sv1, sv2)
# Check that we can retrieve the original structures correctly
extracted_graphs <- get_structure_graphs(combined)
expect_length(extracted_graphs, 2)
expect_s3_class(extracted_graphs[[1]], "igraph")
expect_s3_class(extracted_graphs[[2]], "igraph")
# Verify structure properties are preserved using known structures
# o_glycan_core_1 has GalNAc and Gal
# n_glycan_core has Man and GlcNAc
expect_true("GalNAc" %in% igraph::V(extracted_graphs[[1]])$mono)
expect_true("Gal" %in% igraph::V(extracted_graphs[[1]])$mono)
expect_true("Man" %in% igraph::V(extracted_graphs[[2]])$mono)
expect_true("GlcNAc" %in% igraph::V(extracted_graphs[[2]])$mono)
})
# Tests for vector casting and subsetting functionality -------------------------
test_that("glycan_structure vectors can be subset with necessary structure preservation", {
sv <- c(o_glycan_core_1(), n_glycan_core(), o_glycan_core_1())
# Test various subsetting operations
subset1 <- sv[1]
subset2 <- sv[c(1, 3)]
subset3 <- sv[2:3]
subset4 <- sv[c()]
subset5 <- sv[integer(0)]
expect_s3_class(subset1, "glyrepr_structure")
expect_s3_class(subset2, "glyrepr_structure")
expect_s3_class(subset3, "glyrepr_structure")
expect_s3_class(subset4, "glyrepr_structure")
expect_s3_class(subset5, "glyrepr_structure")
expect_equal(length(subset1), 1)
expect_equal(length(subset2), 2)
expect_equal(length(subset3), 2)
expect_equal(length(subset4), 0)
expect_equal(length(subset5), 0)
# Check that unique structure tracking is maintained
expect_length(attr(sv, "graphs"), 2) # Original has 2 unique
expect_length(attr(subset1, "graphs"), 1) # Subset preserves 1 unique
expect_length(attr(subset2, "graphs"), 1) # Subset preserves 1 unique
expect_length(attr(subset3, "graphs"), 2) # Subset preserves 2 unique
expect_length(attr(subset4, "graphs"), 0) # Subset preserves 0 unique
expect_length(attr(subset5, "graphs"), 0) # Subset preserves 0 unique
})
test_that("glycan_structure vectors can be repeated", {
sv <- o_glycan_core_1()
# Test rep() function which uses vctrs casting methods
repeated <- rep(sv, 3)
expect_s3_class(repeated, "glyrepr_structure")
expect_equal(length(repeated), 3)
expect_length(attr(repeated, "graphs"), 1) # Still only 1 unique structure
formatted <- format(repeated)
expect_equal(formatted, rep("Gal(b1-3)GalNAc(a1-", 3))
})
test_that("complex vector operations work correctly", {
# Test more complex vector operations
sv1 <- c(o_glycan_core_1(), n_glycan_core())
sv2 <- c(n_glycan_core(), o_glycan_core_1())
# Combine and then subset
combined <- c(sv1, sv2)
reordered <- combined[c(4, 3, 2, 1)]
expect_s3_class(reordered, "glyrepr_structure")
expect_equal(length(reordered), 4)
expect_length(attr(reordered, "graphs"), 2)
# Check that reordering worked correctly
formatted_original <- format(combined)
formatted_reordered <- format(reordered)
expect_equal(formatted_reordered, rev(formatted_original))
})
test_that("structure vector methods handle edge cases", {
# Test with single element vector
single <- o_glycan_core_1()
# Test combining with itself
doubled <- c(single, single)
expect_equal(length(doubled), 2)
expect_length(attr(doubled, "graphs"), 1)
# Test empty + non-empty combinations in different orders
empty <- glycan_structure()
combined1 <- c(empty, single, empty)
combined2 <- c(single, empty, single)
expect_equal(length(combined1), 1)
expect_equal(length(combined2), 2)
expect_length(attr(combined1, "graphs"), 1)
expect_length(attr(combined2, "graphs"), 1)
})
# Tests for tibble and dplyr operations with structure optimization -----------
test_that("tibble row subsetting optimizes structure storage", {
skip_if_not_installed("tibble")
# Create test data with duplicates
sv <- c(o_glycan_core_1(), n_glycan_core(), o_glycan_core_1())
df <- tibble::tibble(id = 1:3, structure = sv, name = c("A", "B", "C"))
# Original should have 2 unique structures
expect_length(attr(sv, "graphs"), 2)
# Single row subsetting should optimize to 1 unique structure
subset1 <- df[1, ]
expect_equal(length(subset1$structure), 1)
expect_length(attr(subset1$structure, "graphs"), 1)
# Multiple row subsetting with same structure should optimize to 1 unique
subset2 <- df[c(1, 3), ] # Both point to same structure
expect_equal(length(subset2$structure), 2)
expect_length(attr(subset2$structure, "graphs"), 1)
# Multiple row subsetting with different structures should keep both
subset3 <- df[2:3, ]
expect_equal(length(subset3$structure), 2)
expect_length(attr(subset3$structure, "graphs"), 2)
# Single row with different structure
subset4 <- df[2, ]
expect_equal(length(subset4$structure), 1)
expect_length(attr(subset4$structure, "graphs"), 1)
})
test_that("dplyr filter operations optimize structure storage", {
skip_if_not_installed("dplyr")
# Create test data with duplicates
sv <- c(o_glycan_core_1(), n_glycan_core(), o_glycan_core_1())
df <- tibble::tibble(id = 1:3, structure = sv, score = c(10, 20, 30))
# Filter to single row should optimize to 1 unique structure
filtered1 <- df %>% dplyr::filter(id == 1)
expect_equal(length(filtered1$structure), 1)
expect_length(attr(filtered1$structure, "graphs"), 1)
# Filter to multiple rows with same structure should optimize
filtered2 <- df %>% dplyr::filter(id != 2) # Keeps rows 1 and 3 (same structure)
expect_equal(length(filtered2$structure), 2)
expect_length(attr(filtered2$structure, "graphs"), 1)
# Filter to multiple rows with different structures should keep both
filtered3 <- df %>% dplyr::filter(id >= 2) # Keeps rows 2 and 3 (different structures)
expect_equal(length(filtered3$structure), 2)
expect_length(attr(filtered3$structure, "graphs"), 2)
# Filter by score
filtered4 <- df %>% dplyr::filter(score >= 20)
expect_equal(length(filtered4$structure), 2)
expect_length(attr(filtered4$structure, "graphs"), 2)
})
test_that("dplyr slice operations optimize structure storage", {
skip_if_not_installed("dplyr")
# Create test data
sv <- c(o_glycan_core_1(), n_glycan_core(), o_glycan_core_1())
df <- tibble::tibble(id = 1:3, structure = sv)
# slice() operations
sliced1 <- df %>% dplyr::slice(1)
expect_equal(length(sliced1$structure), 1)
expect_length(attr(sliced1$structure, "graphs"), 1)
sliced2 <- df %>% dplyr::slice(c(1, 3))
expect_equal(length(sliced2$structure), 2)
expect_length(attr(sliced2$structure, "graphs"), 1)
sliced3 <- df %>% dplyr::slice(2:3)
expect_equal(length(sliced3$structure), 2)
expect_length(attr(sliced3$structure, "graphs"), 2)
# slice_head() and slice_tail()
head_slice <- df %>% dplyr::slice_head(n = 1)
expect_equal(length(head_slice$structure), 1)
expect_length(attr(head_slice$structure, "graphs"), 1)
tail_slice <- df %>% dplyr::slice_tail(n = 1)
expect_equal(length(tail_slice$structure), 1)
expect_length(attr(tail_slice$structure, "graphs"), 1)
})
test_that("dplyr arrange and other operations preserve structure optimization", {
skip_if_not_installed("dplyr")
# Create test data
sv <- c(o_glycan_core_1(), n_glycan_core(), o_glycan_core_1())
df <- tibble::tibble(id = 1:3, structure = sv, score = c(30, 10, 20))
# arrange() should maintain all structures
arranged <- df %>% dplyr::arrange(score)
expect_equal(length(arranged$structure), 3)
expect_length(attr(arranged$structure, "graphs"), 2)
# arrange() + slice() should optimize
arranged_sliced <- df %>% dplyr::arrange(score) %>% dplyr::slice(1)
expect_equal(length(arranged_sliced$structure), 1)
expect_length(attr(arranged_sliced$structure, "graphs"), 1)
# top_n() operations
top2 <- df %>% dplyr::top_n(2, score)
expect_equal(length(top2$structure), 2)
expect_length(attr(top2$structure, "graphs"), 1) # top_n selects id=1 and id=3, both have same structure
# distinct() operations with duplicated structures
df_with_dups <- tibble::tibble(
id = rep(1:3, each = 2),
structure = rep(sv, each = 2)
)
distinct_result <- df_with_dups %>%
dplyr::distinct(structure, .keep_all = TRUE)
expect_equal(length(distinct_result$structure), 2)
expect_length(attr(distinct_result$structure, "graphs"), 2)
})
test_that("complex tibble and dplyr workflows maintain optimization", {
skip_if_not_installed("dplyr")
# Create more complex test data
sv <- c(
o_glycan_core_1(),
n_glycan_core(),
o_glycan_core_1(),
n_glycan_core(),
o_glycan_core_1()
)
df <- tibble::tibble(
id = 1:5,
structure = sv,
type = c("A", "B", "A", "B", "A"),
score = c(10, 20, 15, 25, 30)
)
# Original should have 2 unique structures
expect_length(attr(sv, "graphs"), 2)
# Complex workflow: filter + arrange + slice
result1 <- df %>%
dplyr::filter(type == "A") %>%
dplyr::arrange(desc(score)) %>%
dplyr::slice(1:2)
expect_equal(length(result1$structure), 2)
expect_length(attr(result1$structure, "graphs"), 1) # All type A have same structure
# Another complex workflow: group operations
result2 <- df %>%
dplyr::group_by(type) %>%
dplyr::slice_max(score, n = 1) %>%
dplyr::ungroup()
expect_equal(length(result2$structure), 2)
expect_length(attr(result2$structure, "graphs"), 2) # One from each type
# Workflow with structure column operations
result3 <- df %>%
dplyr::filter(score >= 20) %>%
dplyr::select(structure, score)
expect_equal(length(result3$structure), 3) # score >= 20 selects 3 rows (id=2,4,5)
expect_length(attr(result3$structure, "graphs"), 2) # These have 2 different structures
})
test_that("tibble operations preserve structure content integrity", {
skip_if_not_installed("dplyr")
# Create test data
sv <- c(o_glycan_core_1(), n_glycan_core(), o_glycan_core_1())
df <- tibble::tibble(id = 1:3, structure = sv)
# Verify that optimization doesn't affect structure content
subset_df <- df %>% dplyr::filter(id == 1)
# Extract the structure and verify it's correct
structure_graph <- get_structure_graphs(
subset_df$structure,
return_list = FALSE
)
expect_s3_class(structure_graph, "igraph")
# Verify structure content (o_glycan_core_1 has GalNAc and Gal)
expect_true("GalNAc" %in% igraph::V(structure_graph)$mono)
expect_true("Gal" %in% igraph::V(structure_graph)$mono)
# Verify IUPAC representation is preserved
expect_equal(format(subset_df$structure)[1], "Gal(b1-3)GalNAc(a1-")
})
# Tests for vector conversion -------------------------
test_that("converting to character", {
sv <- o_glycan_core_1()
expect_equal(as.character(sv), "Gal(b1-3)GalNAc(a1-")
})
test_that("[[ extracts named structure elements by name", {
glycans <- c(valid = o_glycan_core_1(), missing = glycan_structure(NA))
expect_false(is.na(glycans[["valid"]]))
expect_equal(as.character(glycans[["valid"]]), "Gal(b1-3)GalNAc(a1-")
})
# Tests for vertex and edge reordering -------------------------
test_that("vertices and edges are reordered correctly", {
graph1 <- igraph::make_graph(~ 1 - +2, 1 - +3)
igraph::V(graph1)$mono <- c("GalNAc", "Gal", "GlcNAc")
igraph::V(graph1)$sub <- ""
igraph::E(graph1)$linkage <- c("b1-3", "b1-6")
graph1$anomer <- "a1"
graph2 <- igraph::make_graph(~ 1 - +2, 1 - +3)
igraph::V(graph2)$mono <- c("GalNAc", "GlcNAc", "Gal")
igraph::V(graph2)$sub <- ""
igraph::E(graph2)$linkage <- c("b1-6", "b1-3")
graph2$anomer <- "a1"
sv1 <- glycan_structure(graph1)
sv2 <- glycan_structure(graph2)
graph1 <- get_structure_graphs(sv1, return_list = FALSE)
graph2 <- get_structure_graphs(sv2, return_list = FALSE)
expect_equal(igraph::V(graph1)$mono, c("Gal", "GlcNAc", "GalNAc"))
expect_equal(igraph::V(graph2)$mono, c("Gal", "GlcNAc", "GalNAc"))
expect_equal(igraph::E(graph1)$linkage, c("b1-3", "b1-6"))
expect_equal(igraph::E(graph2)$linkage, c("b1-3", "b1-6"))
})
# Tests for mono_type validation in glycan_structure -------------------------
test_that("glycan_structure accepts multiple concrete structures", {
graph1 <- create_simple_glycan_graph(c("Glc", "Gal"), "b1-4")
graph2 <- create_simple_glycan_graph(c("Man", "GlcNAc"), "b1-4")
sv <- glycan_structure(graph1, graph2)
expect_equal(length(sv), 2)
})
test_that("glycan_structure accepts multiple generic structures", {
graph1 <- create_simple_glycan_graph(c("Hex", "HexNAc"), "b1-4")
graph2 <- create_simple_glycan_graph(c("Hex", "dHex"), "b1-6")
sv <- glycan_structure(graph1, graph2)
expect_equal(length(sv), 2)
})
test_that("glycan_structure accepts concrete and generic structures", {
graph1 <- create_simple_glycan_graph(c("Glc", "Gal"), "b1-4") # concrete
graph2 <- create_simple_glycan_graph(c("Hex", "HexNAc"), "b1-4") # generic
sv <- glycan_structure(graph1, graph2)
expect_identical(get_mono_type(sv), c("concrete", "generic"))
})
test_that("c() combines concrete and generic structure vectors", {
sv1 <- o_glycan_core_1() # concrete: Gal, GalNAc
sv2 <- n_glycan_core(mono_type = "generic") # generic: Hex, HexNAc
expect_identical(get_mono_type(c(sv1, sv2)), c("concrete", "generic"))
})
test_that("get_mono_type returns same type for all structures in vector", {
graph1 <- create_simple_glycan_graph(c("Glc", "Gal"), "b1-4")
graph2 <- create_simple_glycan_graph(c("Man", "GlcNAc"), "b1-4")
sv <- glycan_structure(graph1, graph2)
expect_equal(get_mono_type(sv), c("concrete", "concrete"))
})
test_that("get_mono_type returns same type for generic structures", {
graph1 <- create_simple_glycan_graph(c("Hex", "HexNAc"), "b1-4")
graph2 <- create_simple_glycan_graph(c("Hex", "dHex"), "b1-6")
sv <- glycan_structure(graph1, graph2)
expect_equal(get_mono_type(sv), c("generic", "generic"))
})
# Tests for [[<- operation (issue #11) -----------------------------------------
test_that("[[<- is forbidden on glyrepr_structure vectors", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
expect_error(
glycans[[1]] <- n_glycan_core(),
class = "rlang_error"
)
})
test_that("[[<- is forbidden with igraph value", {
glycans <- o_glycan_core_1()
graph <- get_structure_graphs(glycans, return_list = FALSE)
expect_error(
glycans[[1]] <- graph,
class = "rlang_error"
)
})
# Tests for names-----
test_that("directly setting names works", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
names(glycans) <- c("A", "B")
expect_equal(names(glycans), c("A", "B"))
})
test_that("new structures have NULL names by default", {
glycans <- o_glycan_core_1()
expect_null(names(glycans))
})
test_that("new structure vectors have NULL names by default", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
expect_null(names(glycans))
})
test_that("names are preserved after subsetting with [", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
names(glycans) <- c("A", "B")
subset1 <- glycans[1]
expect_equal(names(subset1), "A")
subset2 <- glycans[2]
expect_equal(names(subset2), "B")
subset_both <- glycans[c(1, 2)]
expect_equal(names(subset_both), c("A", "B"))
subset_reverse <- glycans[c(2, 1)]
expect_equal(names(subset_reverse), c("B", "A"))
})
test_that("names are preserved after character subsetting with [", {
glycans <- c(valid = o_glycan_core_1(), missing = glycan_structure(NA))
subset <- glycans["valid"]
expect_false(is.na(subset))
expect_equal(names(subset), "valid")
expect_equal(as.character(subset), c(valid = "Gal(b1-3)GalNAc(a1-"))
})
test_that("names are preserved after subsetting with logical index", {
glycans <- c(o_glycan_core_1(), n_glycan_core(), o_glycan_core_1())
names(glycans) <- c("A", "B", "C")
subset_logical <- glycans[c(TRUE, FALSE, TRUE)]
expect_equal(names(subset_logical), c("A", "C"))
})
test_that("names are preserved after c() combining", {
glycans1 <- o_glycan_core_1()
glycans2 <- n_glycan_core()
names(glycans1) <- "A"
names(glycans2) <- "B"
combined <- c(glycans1, glycans2)
expect_equal(names(combined), c("A", "B"))
})
test_that("names are preserved after rep()", {
glycans <- o_glycan_core_1()
names(glycans) <- "A"
repeated <- rep(glycans, 3)
expect_equal(names(repeated), c("A", "A", "A"))
})
test_that("setting names to NULL removes names", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
names(glycans) <- c("A", "B")
expect_equal(names(glycans), c("A", "B"))
names(glycans) <- NULL
expect_null(names(glycans))
})
test_that("names work with empty vectors", {
empty <- glycan_structure()
expect_null(names(empty))
})
test_that("names work with single-element vectors", {
glycan <- o_glycan_core_1()
names(glycan) <- "single"
expect_equal(names(glycan), "single")
})
test_that("names are preserved after combining vectors with different names", {
glycans1 <- c(o_glycan_core_1())
glycans2 <- c(n_glycan_core())
names(glycans1) <- "A"
names(glycans2) <- "B"
combined <- c(glycans1, glycans2)
expect_equal(names(combined), c("A", "B"))
})
test_that("names work with duplicated structures", {
glycans <- c(o_glycan_core_1(), n_glycan_core(), o_glycan_core_1())
names(glycans) <- c("A", "B", "C")
# First and third point to the same structure but have different names
expect_equal(names(glycans), c("A", "B", "C"))
# Subsetting should preserve names correctly
subset <- glycans[c(1, 3)]
expect_equal(names(subset), c("A", "C"))
})
test_that("names are preserved in tibble operations", {
skip_if_not_installed("tibble")
glycans <- c(o_glycan_core_1(), n_glycan_core())
names(glycans) <- c("A", "B")
df <- tibble::tibble(id = 1:2, structure = glycans)
# Subsetting tibble should preserve names
subset_df <- df[1, ]
expect_equal(names(subset_df$structure), "A")
subset_df2 <- df[2, ]
expect_equal(names(subset_df2$structure), "B")
})
test_that("names work correctly with dplyr operations", {
skip_if_not_installed("dplyr")
glycans <- c(o_glycan_core_1(), n_glycan_core())
names(glycans) <- c("A", "B")
df <- tibble::tibble(id = 1:2, structure = glycans)
# filter should preserve names
filtered <- df %>% dplyr::filter(id == 1)
expect_equal(names(filtered$structure), "A")
# slice should preserve names
sliced <- df %>% dplyr::slice(2)
expect_equal(names(sliced$structure), "B")
})
test_that("unname removes names", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
names(glycans) <- c("A", "B")
unname_glycans <- unname(glycans)
expect_null(names(unname_glycans))
})
test_that("names work with character conversion", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
names(glycans) <- c("A", "B")
# Converting to character should preserve names
chars <- as.character(glycans)
expect_equal(names(chars), c("A", "B"))
})
# Comprehensive regression tests for names preservation in glyrepr_structure functions
test_that("all glyrepr_structure functions preserve names", {
core1 <- o_glycan_core_1()
core2 <- n_glycan_core()
structures <- c(core1, core2, core1)
names(structures) <- c("A", "B", "C")
# Type conversion (glyrepr_structure -> glyrepr_structure)
expect_equal(names(as_glycan_structure(structures)), c("A", "B", "C"))
# Character to glyrepr_structure conversion
char_vec <- c(X = "Glc(a1-", Y = "Gal(a1-")
expect_equal(names(as_glycan_structure(char_vec)), c("X", "Y"))
# Accessor functions (return atomic vectors)
expect_equal(names(get_anomer(structures)), c("A", "B", "C"))
expect_equal(names(has_linkages(structures)), c("A", "B", "C"))
expect_equal(names(count_mono(structures)), c("A", "B", "C"))
expect_equal(names(structure_to_iupac(structures)), c("A", "B", "C"))
expect_equal(names(get_structure_level(structures)), c("A", "B", "C"))
# Accessor functions (return list)
expect_equal(names(get_structure_graphs(structures)), c("A", "B", "C"))
# Transformation functions (return glyrepr_structure)
expect_equal(names(remove_linkages(structures)), c("A", "B", "C"))
expect_equal(names(remove_substituents(structures)), c("A", "B", "C"))
# Composition conversion
structs_concrete <- c(o_glycan_core_1(), n_glycan_core())
names(structs_concrete) <- c("X", "Y")
expect_equal(names(convert_to_generic(structs_concrete)), c("X", "Y"))
# Vector combination
expect_equal(names(c(structures)), c("A", "B", "C"))
})
# Tests for is.na method ----------------------------------------------------
test_that("is.na returns correct logical for structures with NA", {
struct <- o_glycan_core_1()
expect_equal(is.na(struct), FALSE)
struct_na <- c(o_glycan_core_1(), NA)
expect_equal(is.na(struct_na), c(FALSE, TRUE))
struct_all_na <- c(NA, NA)
expect_equal(is.na(struct_all_na), c(TRUE, TRUE))
})
# Tests for NULL/NA handling in glycan_structure -----------------------------
test_that("glycan_structure accepts NULL to create NA", {
struct <- glycan_structure(NULL)
expect_equal(length(struct), 1)
expect_true(is.na(struct))
})
test_that("glycan_structure accepts NA to create NA", {
struct <- glycan_structure(NA)
expect_equal(length(struct), 1)
expect_true(is.na(struct))
})
test_that("glycan_structure handles mixed valid and NA", {
graph <- o_glycan_core_1() |> get_structure_graphs(return_list = FALSE)
struct <- glycan_structure(graph, NULL, graph)
expect_equal(length(struct), 3)
expect_false(is.na(struct[1]))
expect_true(is.na(struct[2]))
expect_false(is.na(struct[3]))
})
# Tests for vec_cast.character with NA handling --------------------------------
test_that("vec_cast handles NA characters", {
chars <- c("Glc(a1-", NA)
struct <- as_glycan_structure(chars)
expect_equal(length(struct), 2)
expect_false(is.na(struct[1]))
expect_true(is.na(struct[2]))
})
test_that("vec_cast handles NA at beginning", {
chars <- c(NA, "Gal(b1-3)GalNAc(a1-")
struct <- as_glycan_structure(chars)
expect_equal(length(struct), 2)
expect_true(is.na(struct[1]))
expect_false(is.na(struct[2]))
})
test_that("vec_cast handles all NA characters", {
chars <- c(NA, NA)
struct <- as_glycan_structure(chars)
expect_equal(length(struct), 2)
expect_true(is.na(struct[1]))
expect_true(is.na(struct[2]))
})
# Tests for vec_restore NA handling -----------------------------------------
test_that("vec_restore skips NA in type checking", {
struct1 <- o_glycan_core_1()
struct2 <- glycan_structure(NA)
# Should not error - NA should be skipped in type check
combined <- c(struct1, struct2)
expect_equal(length(combined), 2)
expect_false(is.na(combined[1]))
expect_true(is.na(combined[2]))
})
test_that("combining with NA at beginning works", {
struct <- o_glycan_core_1()
# c(NA, struct) doesn't preserve type in base R
combined <- vctrs::vec_c(NA, struct)
expect_equal(length(combined), 2)
expect_true(is.na(combined[1]))
expect_false(is.na(combined[2]))
})
test_that("vec_restore handles all NA vector", {
struct_na <- glycan_structure(NA, NA)
expect_equal(length(struct_na), 2)
expect_equal(is.na(struct_na), c(TRUE, TRUE))
expect_length(attr(struct_na, "graphs"), 0) # No graphs for NA elements
})
test_that("vec_restore preserves graphs for non-NA with mixed NA", {
struct1 <- o_glycan_core_1()
struct2 <- n_glycan_core()
combined <- c(struct1, NA, struct2)
expect_equal(length(combined), 3)
expect_false(is.na(combined[1]))
expect_true(is.na(combined[2]))
expect_false(is.na(combined[3]))
# Should have 2 unique structures (not counting NA)
expect_length(attr(combined, "graphs"), 2)
})
# Tests for as_glycan_composition with NA structures ---------------------------------
test_that("as_glycan_composition handles structures with NA", {
structs <- c(o_glycan_core_1(), NA, n_glycan_core())
comps <- as_glycan_composition(structs)
expect_equal(length(comps), 3)
expect_false(is.na(comps[1]))
expect_true(is.na(comps[2]))
expect_false(is.na(comps[3]))
})
test_that("as_glycan_composition handles single NA structure", {
structs <- glycan_structure(NA)
comps <- as_glycan_composition(structs)
expect_equal(length(comps), 1)
expect_true(is.na(comps[1]))
})
test_that("as_glycan_composition handles all NA structures", {
structs <- glycan_structure(NA, NA)
comps <- as_glycan_composition(structs)
expect_equal(length(comps), 2)
expect_true(all(is.na(comps)))
})
# Tests for convert_to_generic with NA --------------------------------------------
test_that("convert_to_generic handles single valid structure", {
# Note: convert_to_generic uses smap which currently doesn't handle NA
# This test verifies the function works for non-NA structures
converted <- convert_to_generic(o_glycan_core_1())
expect_false(is.na(converted))
expect_equal(get_mono_type(converted), "generic")
})
test_that("convert_to_generic works on N-glycan core", {
# Note: convert_to_generic uses smap which currently doesn't handle NA
# This test verifies the function works for non-NA structures
converted <- convert_to_generic(n_glycan_core())
expect_false(is.na(converted))
expect_equal(get_mono_type(converted), "generic")
})
# Tests for get_mono_type with NA --------------------------------------------------
test_that("get_mono_type handles mixed NA and valid structures", {
structs <- c(o_glycan_core_1(), NA)
types <- get_mono_type(structs)
expect_identical(types, c("concrete", NA_character_))
})
test_that("get_mono_type handles all NA structures", {
structs <- glycan_structure(NA, NA)
types <- get_mono_type(structs)
expect_identical(types, c(NA_character_, NA_character_))
})
# Tests for rep with NA ------------------------------------------------------------
test_that("rep handles structures with NA", {
struct <- c(o_glycan_core_1(), NA)
repeated <- rep(struct, 2)
expect_equal(length(repeated), 4)
expect_equal(is.na(repeated), c(FALSE, TRUE, FALSE, TRUE))
})
test_that("rep preserves NA in single-element NA structure", {
struct <- glycan_structure(NA)
repeated <- rep(struct, 3)
expect_equal(length(repeated), 3)
expect_true(all(is.na(repeated)))
})
# Tests for subsetting preserves NA ------------------------------------------------
test_that("subsetting preserves NA", {
struct <- c(o_glycan_core_1(), NA, n_glycan_core())
expect_true(is.na(struct[2]))
expect_equal(is.na(struct[c(1, 3)]), c(FALSE, FALSE))
expect_equal(length(struct[c(1, 3)]), 2)
})
test_that("subsetting with logical index preserves NA", {
struct <- c(o_glycan_core_1(), NA, n_glycan_core())
subset <- struct[c(TRUE, FALSE, TRUE)]
expect_equal(length(subset), 2)
expect_false(is.na(subset[1]))
expect_false(is.na(subset[2]))
})
test_that("subsetting removes NA elements correctly", {
struct <- c(o_glycan_core_1(), NA, n_glycan_core())
subset <- struct[!is.na(struct)]
expect_equal(length(subset), 2)
expect_false(any(is.na(subset)))
})
# Tests for format with NA ---------------------------------------------------------
test_that("format preserves order with mixed NA and valid", {
struct1 <- o_glycan_core_1()
struct2 <- n_glycan_core()
structs <- c(struct1, NA, struct2, NA)
formatted <- format(structs)
expect_false(is.na(formatted[1]))
expect_true(grepl("^NA", formatted[2])) # format() pads with spaces
expect_false(is.na(formatted[3]))
expect_true(grepl("^NA", formatted[4])) # format() pads with spaces
})
test_that("format handles all NA structures", {
structs <- glycan_structure(NA, NA)
formatted <- format(structs)
expect_equal(length(formatted), 2)
expect_true(all(grepl("^NA", formatted))) # format() pads with spaces
})
# Tests for tibble printing with NA ------------------------------------------------
test_that("tibble printing handles NA structures", {
skip_if_not_installed("tibble")
struct <- c(o_glycan_core_1(), NA)
df <- tibble::tibble(struct = struct, id = 1:2)
output <- capture.output(print(df))
# NA is displayed as "NA" string in tibble
expect_true(any(grepl("NA", output)))
})
test_that("tibble printing handles multiple NA structures", {
skip_if_not_installed("tibble")
struct <- c(o_glycan_core_1(), NA, n_glycan_core(), NA)
df <- tibble::tibble(struct = struct, id = 1:4)
output <- capture.output(print(df))
# Count occurrences of "NA" string (appears once per NA element in data rows)
na_count <- sum(grepl("NA", output))
expect_true(na_count >= 2) # At least two NA entries should appear
})
# Tests for c() combining with NA --------------------------------------------------
test_that("combining multiple structures with NA preserves type", {
struct1 <- o_glycan_core_1()
struct2 <- n_glycan_core()
combined <- c(struct1, NA, struct2)
expect_s3_class(combined, "glyrepr_structure")
expect_equal(length(combined), 3)
expect_false(is.na(combined[1]))
expect_true(is.na(combined[2]))
expect_false(is.na(combined[3]))
})
test_that("c() combines all-NA vectors", {
structs <- glycan_structure(NA, NA)
combined <- c(structs, glycan_structure(NA))
expect_equal(length(combined), 3)
expect_true(all(is.na(combined)))
})
# Tests for all-NA vector ----------------------------------------------------------
test_that("all NA structure vector has correct properties", {
structs <- glycan_structure(NA, NA)
expect_equal(length(structs), 2)
expect_true(all(is.na(structs)))
expect_equal(length(attr(structs, "graphs")), 0) # No graphs for NA
})
# Tests for vec_slice with NA ------------------------------------------------------
test_that("vec_slice preserves NA", {
struct <- c(o_glycan_core_1(), NA, n_glycan_core())
sliced <- vctrs::vec_slice(struct, c(1, 3))
expect_equal(length(sliced), 2)
expect_false(is.na(sliced[1]))
expect_false(is.na(sliced[2]))
})
test_that("vec_slice can select NA elements", {
struct <- c(o_glycan_core_1(), NA, n_glycan_core())
sliced <- vctrs::vec_slice(struct, 2)
expect_equal(length(sliced), 1)
expect_true(is.na(sliced[1]))
})
test_that("vec_slice with negative index excludes NA correctly", {
struct <- c(o_glycan_core_1(), NA, n_glycan_core())
sliced <- vctrs::vec_slice(struct, -2)
expect_equal(length(sliced), 2)
expect_false(any(is.na(sliced)))
})
# Tests for structure_to_iupac with NA ---------------------------------------------
test_that("structure_to_iupac handles structures with NA", {
structs <- c(o_glycan_core_1(), NA, n_glycan_core())
iupacs <- structure_to_iupac(structs)
expect_equal(length(iupacs), 3)
expect_false(is.na(iupacs[1]))
expect_true(is.na(iupacs[2]))
expect_false(is.na(iupacs[3]))
})
# Tests for remove_linkages with NA -------------------------------------------------
test_that("remove_linkages handles single valid structure", {
# Note: remove_linkages uses smap which currently doesn't handle NA
# This test verifies the function works for non-NA structures
removed <- remove_linkages(o_glycan_core_1())
expect_equal(has_linkages(removed), FALSE)
})
# Tests for has_linkages with NA ---------------------------------------------------
test_that("has_linkages handles single valid structure", {
# Note: has_linkages uses smap which currently doesn't handle NA
# This test verifies the function works for non-NA structures
result <- has_linkages(o_glycan_core_1())
expect_equal(result, TRUE)
})
# Tests for get_anomer with NA ------------------------------------------------------
test_that("get_anomer handles single valid structure", {
# Note: get_anomer uses smap which currently doesn't handle NA
# This test verifies the function works for non-NA structures
anomer <- get_anomer(o_glycan_core_1())
expect_equal(anomer, "a1")
})
# Tests for count_mono with NA ------------------------------------------------------
test_that("count_mono handles single valid structure", {
# Note: count_mono uses smap which currently doesn't handle NA
# This test verifies the function works for non-NA structures
count <- count_mono(o_glycan_core_1())
expect_equal(length(count), 1)
expect_false(is.na(count))
})
# Tests for class duplication fix (issue #30) -------------------------------------
test_that("class is not duplicated after c()", {
g1 <- o_glycan_core_1()
g2 <- n_glycan_core()
# Individual structures should have correct class
expect_true(inherits(g1, "glyrepr_structure"))
expect_equal(sum(class(g1) == "vctrs_vctr"), 1)
expect_true(inherits(g2, "glyrepr_structure"))
expect_equal(sum(class(g2) == "vctrs_vctr"), 1)
# After c(), class should still be correct (not duplicated)
combined <- c(g1, g2)
expect_true(inherits(combined, "glyrepr_structure"))
expect_equal(sum(class(combined) == "vctrs_vctr"), 1)
expect_equal(length(unique(class(combined))), length(class(combined)))
})
test_that("class is not duplicated after subsetting", {
glycans <- c(o_glycan_core_1(), n_glycan_core())
# Before subsetting
expect_true(inherits(glycans, "glyrepr_structure"))
expect_equal(sum(class(glycans) == "vctrs_vctr"), 1)
expect_equal(length(unique(class(glycans))), length(class(glycans)))
# After subsetting
subset1 <- glycans[1]
expect_true(inherits(subset1, "glyrepr_structure"))
expect_equal(sum(class(subset1) == "vctrs_vctr"), 1)
expect_equal(length(unique(class(subset1))), length(class(subset1)))
subset2 <- glycans[c(1, 2)]
expect_true(inherits(subset2, "glyrepr_structure"))
expect_equal(sum(class(subset2) == "vctrs_vctr"), 1)
expect_equal(length(unique(class(subset2))), length(class(subset2)))
subset_empty <- glycans[integer(0)]
expect_true(inherits(subset_empty, "glyrepr_structure"))
expect_equal(sum(class(subset_empty) == "vctrs_vctr"), 1)
expect_equal(length(unique(class(subset_empty))), length(class(subset_empty)))
})
test_that("vec_restore does not duplicate class", {
g1 <- o_glycan_core_1()
g2 <- n_glycan_core()
ptype <- vctrs::vec_ptype2(g1, g2)
# Prototype should have correct class
expect_true(inherits(ptype, "glyrepr_structure"))
expect_equal(sum(class(ptype) == "vctrs_vctr"), 1)
# vec_restore should not duplicate class
restored <- vctrs::vec_restore(g1, ptype)
expect_true(inherits(restored, "glyrepr_structure"))
expect_equal(sum(class(restored) == "vctrs_vctr"), 1)
expect_equal(length(unique(class(restored))), length(class(restored)))
})
test_that("as_glycan_structure treats scalar NA character as missing structure", {
result <- as_glycan_structure(NA_character_)
expect_s3_class(result, "glyrepr_structure")
expect_length(result, 1)
expect_true(is.na(result))
expect_equal(attr(result, "graphs"), list())
})
test_that("get_structure_level preserves NA structures", {
structures <- c(o_glycan_core_1(), glycan_structure(NA))
result <- get_structure_level(structures)
expect_identical(result, c("intact", NA_character_))
})
test_that("remove_linkages preserves NA structures", {
structures <- c(o_glycan_core_1(), glycan_structure(NA))
result <- remove_linkages(structures)
expect_false(has_linkages(result[1]))
expect_true(is.na(result[2]))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.