diff --git a/DESCRIPTION b/DESCRIPTION index 7ed51f39..ee66b4eb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,6 +26,7 @@ Imports: magrittr, rlang, stringi (>= 1.5.3), + vctrs, withr Suggests: covr, diff --git a/NEWS.md b/NEWS.md index 861fb1ec..b21ed904 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,18 @@ # stringr (development version) +* stringr functions now consistently implement the tidyverse recycling rules + (#372). Overall this is a fairly minor change as stringi was already very + close to the tidyverse rules. There are only two major changes: + + * Only vectors of length 1 are recycled: previously, + `str_detect(letters, c("x", "y"))` worked, but it now errors. + + * `str_c()` ignores `NULLs`, rather than treating them as length 0 + vectors. + + Additionally, many more non-vectorised arguments now throw errors, + rather than warnings, if supplied a vector. + * `str_flatten()` gains a `last` argument that optionally override the final separator (#377). @@ -15,8 +28,7 @@ requires a pattern so you can use it to display strings with special characters. -* `str_c()` and `str_length()` are superseded in favour of `paste0()` and - `nchar()` respectively (#356). +* `str_length()` is superseded in favour of `nchar()` respectively (#356). * `str_wrap()` breaks only at whitespace by default; set `whitespace_only = FALSE` to return to the previous behaviour (#335, @rjpat). diff --git a/R/c.r b/R/c.r index 02c8f4ca..383575d3 100644 --- a/R/c.r +++ b/R/c.r @@ -1,35 +1,32 @@ #' Join multiple strings into a single string #' #' @description -#' `r lifecycle::badge("superseded")` +#' `str_c()` combines multiple character vectors into a single character +#' vector. It's very similar to [`paste0()`] but uses tidyverse recycling and +#' `NA` rules. #' -#' `str_c()` is no longer needed; please use `paste0()` instead. +#' One way to understand how `str_c()` works is picture a 2d matrix of strings, +#' where each argument forms a column. `sep` is inserted between each column, +#' and then each row is combined together into a single string. If `collapse` +#' is set, it's inserted between each row, and then the result is again +#' combined, this time into a single string. #' -#' @details +#' @param ... One or more character vectors. #' -#' To understand how `str_c` works, you need to imagine that you are building up -#' a matrix of strings. Each input argument forms a column, and is expanded to -#' the length of the longest argument, using the usual recyling rules. The -#' `sep` string is inserted between each column. If collapse is `NULL` each row -#' is collapsed into a single string. If non-`NULL` that string is inserted at -#' the end of each row, and the entire matrix collapsed to a single string. -#' -#' @param ... One or more character vectors. Zero length arguments -#' are removed. Short arguments are recycled to the length of the -#' longest. +#' `NULL`s are removed; scalar inputs (vectors of length 1) are recycled to +#' the common length of vector inputs. #' #' Like most other R functions, missing values are "infectious": whenever #' a missing value is combined with another string the result will always -#' be missing. Use [str_replace_na()] to convert `NA` to -#' `"NA"` +#' be missing. Use [dplyr::coalesce()] or [str_replace_na()] to convert +#' desired value. #' @param sep String to insert between input vectors. -#' @param collapse Optional string used to combine input vectors into single -#' string. +#' @param collapse Optional string used to combine output into single +#' string. Generally better to use [str_flatten()] if you needed this +#' behaviour. #' @return If `collapse = NULL` (the default) a character vector with -#' length equal to the longest input string. If `collapse` is -#' non-NULL, a character vector of length 1. -#' @seealso [paste()] for equivalent base R functionality, and -#' [stringi::stri_join()] which this function wraps +#' length equal to the longest input. If `collapse` is a string, a character +#' vector of length 1. #' @export #' @keywords internal #' @examples @@ -41,11 +38,30 @@ #' str_c(letters, collapse = "") #' str_c(letters, collapse = ", ") #' +#' # Differences from paste() ---------------------- #' # Missing inputs give missing outputs #' str_c(c("a", NA, "b"), "-d") +#' paste0(c("a", NA, "b"), "-d") #' # Use str_replace_NA to display literal NAs: #' str_c(str_replace_na(c("a", NA, "b")), "-d") -#' @import stringi +#' +#' # Uses tidyverse recycling rules +#' \dontrun{str_c(1:2, 1:3)} # errors +#' paste0(1:2, 1:3) +#' +#' str_c("x", character()) +#' paste0("x", character()) str_c <- function(..., sep = "", collapse = NULL) { - stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE) + if (!is_string(sep)) { + abort("`sep` must be a single string") + } + if (!is.null(collapse) && !is_string(collapse)) { + abort("`collapse` must be NULL or single string") + } + + dots <- list(...) + dots <- dots[!map_lgl(dots, is.null)] + vctrs::vec_size_common(!!!dots) + + exec(stri_c, !!!dots, sep = sep, collapse = collapse) } diff --git a/R/conv.R b/R/conv.R index de14e80b..42e5871a 100644 --- a/R/conv.R +++ b/R/conv.R @@ -13,5 +13,9 @@ #' str_conv(x, "ISO-8859-2") # Polish "a with ogonek" #' str_conv(x, "ISO-8859-1") # Plus-minus str_conv <- function(string, encoding) { + if (!is_string(encoding)) { + abort("`encoding` must be a single string.") + } + stri_conv(string, encoding, "UTF-8") } diff --git a/R/count.r b/R/count.r index 2febc3a7..2d4399c3 100644 --- a/R/count.r +++ b/R/count.r @@ -21,8 +21,10 @@ #' str_count(c("a.", "...", ".a.a"), ".") #' str_count(c("a.", "...", ".a.a"), fixed(".")) str_count <- function(string, pattern = "") { + check_lengths(string, pattern) + switch(type(pattern), - empty = stri_count_boundaries(string, opts_brkiter = opts(pattern)), + empty = , bound = stri_count_boundaries(string, opts_brkiter = opts(pattern)), fixed = stri_count_fixed(string, pattern, opts_fixed = opts(pattern)), coll = stri_count_coll(string, pattern, opts_collator = opts(pattern)), diff --git a/R/detect.r b/R/detect.r index a7ad4f0c..28889f05 100644 --- a/R/detect.r +++ b/R/detect.r @@ -41,6 +41,8 @@ #' # Returns TRUE if the pattern do NOT match #' str_detect(fruit, "^p", negate = TRUE) str_detect <- function(string, pattern, negate = FALSE) { + check_lengths(string, pattern) + switch(type(pattern), empty = , bound = str_count(string, pattern) > 0 & !negate, @@ -75,16 +77,16 @@ str_detect <- function(string, pattern, negate = FALSE) { #' str_ends(fruit, "e") #' str_ends(fruit, "e", negate = TRUE) str_starts <- function(string, pattern, negate = FALSE) { - switch( - type(pattern), + check_lengths(string, pattern) + + switch(type(pattern), empty = , bound = stop("boundary() patterns are not supported."), fixed = stri_startswith_fixed(string, pattern, negate = negate, opts_fixed = opts(pattern)), - coll = stri_startswith_coll(string, pattern, negate = negate, opts_collator = opts(pattern)), + coll = stri_startswith_coll(string, pattern, negate = negate, opts_collator = opts(pattern)), regex = { pattern2 <- paste0("^(", pattern, ")") - attributes(pattern2) <- attributes(pattern) - str_detect(string, pattern2, negate) + stri_detect_regex(string, pattern2, negate = negate, opts_regex = opts(pattern)) } ) } @@ -92,16 +94,17 @@ str_starts <- function(string, pattern, negate = FALSE) { #' @rdname str_starts #' @export str_ends <- function(string, pattern, negate = FALSE) { + check_lengths(string, pattern) + switch(type(pattern), - empty = , - bound = stop("boundary() patterns are not supported."), - fixed = stri_endswith_fixed(string, pattern, negate = negate, opts_fixed = opts(pattern)), - coll = stri_endswith_coll(string, pattern, negate = negate, opts_collator = opts(pattern)), - regex = { - pattern2 <- paste0("(", pattern, ")$") - attributes(pattern2) <- attributes(pattern) - str_detect(string, pattern2, negate) - } + empty = , + bound = stop("boundary() patterns are not supported."), + fixed = stri_endswith_fixed(string, pattern, negate = negate, opts_fixed = opts(pattern)), + coll = stri_endswith_coll(string, pattern, negate = negate, opts_collator = opts(pattern)), + regex = { + pattern2 <- paste0("(", pattern, ")$") + stri_detect_regex(string, pattern2, negate = negate, opts_regex = opts(pattern)) + } ) } @@ -135,6 +138,7 @@ str_like <- function(string, pattern, ignore_case = TRUE) { } pattern <- regex(like_to_regex(pattern), ignore_case = ignore_case) + check_lengths(string, pattern) stri_detect_regex(string, pattern, opts_regex = opts(pattern)) } diff --git a/R/dup.r b/R/dup.r index 910e74e9..8b8f5f0c 100644 --- a/R/dup.r +++ b/R/dup.r @@ -12,5 +12,6 @@ #' str_dup(fruit, 1:3) #' str_c("ba", str_dup("na", 0:5)) str_dup <- function(string, times) { + vctrs::vec_size_common(string = string, times = times) stri_dup(string, times) } diff --git a/R/extract.r b/R/extract.r index 3b6b4524..02e92965 100644 --- a/R/extract.r +++ b/R/extract.r @@ -28,6 +28,8 @@ #' # Extract all words #' str_extract_all("This is, suprisingly, a sentence.", boundary("word")) str_extract <- function(string, pattern) { + check_lengths(string, pattern) + switch(type(pattern), empty = stri_extract_first_boundaries(string, pattern, opts_brkiter = opts(pattern)), bound = stri_extract_first_boundaries(string, pattern, opts_brkiter = opts(pattern)), @@ -40,6 +42,8 @@ str_extract <- function(string, pattern) { #' @rdname str_extract #' @export str_extract_all <- function(string, pattern, simplify = FALSE) { + check_lengths(string, pattern) + switch(type(pattern), empty = stri_extract_all_boundaries(string, pattern, simplify = simplify, omit_no_match = TRUE, opts_brkiter = opts(pattern)), diff --git a/R/flatten.R b/R/flatten.R index e73adfdb..44cc7c36 100644 --- a/R/flatten.R +++ b/R/flatten.R @@ -14,6 +14,10 @@ #' str_flatten(letters[1:2], ", ", ", and ") #' str_flatten(letters[1], ", ", ", and ") str_flatten <- function(string, collapse = "", last = NULL) { + if (!is_string(collapse)) { + abort("`collapse` must be a single string.") + } + n <- length(string) if (!is.null(last) && n >= 2) { string <- c( diff --git a/R/locate.r b/R/locate.r index efb45509..105d3cb1 100644 --- a/R/locate.r +++ b/R/locate.r @@ -26,8 +26,10 @@ #' # Find location of every character #' str_locate_all(fruit, "") str_locate <- function(string, pattern) { + check_lengths(string, pattern) + switch(type(pattern), - empty = stri_locate_first_boundaries(string, opts_brkiter = opts(pattern)), + empty = , bound = stri_locate_first_boundaries(string, opts_brkiter = opts(pattern)), fixed = stri_locate_first_fixed(string, pattern, opts_fixed = opts(pattern)), coll = stri_locate_first_coll(string, pattern, opts_collator = opts(pattern)), @@ -38,10 +40,11 @@ str_locate <- function(string, pattern) { #' @rdname str_locate #' @export str_locate_all <- function(string, pattern) { + check_lengths(string, pattern) opts <- opts(pattern) switch(type(pattern), - empty = stri_locate_all_boundaries(string, omit_no_match = TRUE, opts_brkiter = opts), + empty = , bound = stri_locate_all_boundaries(string, omit_no_match = TRUE, opts_brkiter = opts), fixed = stri_locate_all_fixed(string, pattern, omit_no_match = TRUE, opts_fixed = opts), regex = stri_locate_all_regex(string, pattern, omit_no_match = TRUE, opts_regex = opts), diff --git a/R/match.r b/R/match.r index 968098d7..6cc9c768 100644 --- a/R/match.r +++ b/R/match.r @@ -38,6 +38,7 @@ str_match <- function(string, pattern) { stop("Can only match regular expressions", call. = FALSE) } + check_lengths(string, pattern) stri_match_first_regex(string, pattern, opts_regex = opts(pattern) @@ -51,6 +52,7 @@ str_match_all <- function(string, pattern) { stop("Can only match regular expressions", call. = FALSE) } + check_lengths(string, pattern) stri_match_all_regex(string, pattern, omit_no_match = TRUE, diff --git a/R/modifiers.r b/R/modifiers.r index 8a0b60bc..9a91610f 100644 --- a/R/modifiers.r +++ b/R/modifiers.r @@ -127,7 +127,7 @@ regex <- function(pattern, ignore_case = FALSE, multiline = FALSE, #' @rdname modifiers boundary <- function(type = c("character", "line_break", "sentence", "word"), skip_word_none = NA, ...) { - type <- match.arg(type) + type <- arg_match(type) if (identical(skip_word_none, NA)) { skip_word_none <- type == "word" diff --git a/R/pad.r b/R/pad.r index 70137a19..ceeae046 100644 --- a/R/pad.r +++ b/R/pad.r @@ -27,7 +27,8 @@ #' # Longer strings are returned unchanged #' str_pad("hadley", 3) str_pad <- function(string, width, side = c("left", "right", "both"), pad = " ", use_length = FALSE) { - side <- match.arg(side) + vctrs::vec_size_common(string = string, width = width, pad = pad) + side <- arg_match(side) switch(side, left = stri_pad_left(string, width, pad = pad, use_length = use_length), diff --git a/R/replace.r b/R/replace.r index d718a84e..472b75b9 100644 --- a/R/replace.r +++ b/R/replace.r @@ -69,6 +69,8 @@ str_replace <- function(string, pattern, replacement) { return(str_transform(string, pattern, replacement)) } + check_lengths(string, pattern, replacement) + switch(type(pattern), empty = stop("Empty `pattern` not supported", call. = FALSE), bound = stop("Boundary `pattern` not supported", call. = FALSE), @@ -98,6 +100,8 @@ str_replace_all <- function(string, pattern, replacement) { vec <- TRUE } + check_lengths(string, pattern, replacement) + switch(type(pattern), empty = stop("Empty `pattern`` not supported", call. = FALSE), bound = stop("Boundary `pattern` not supported", call. = FALSE), diff --git a/R/split.r b/R/split.r index f397e7e8..a519070d 100644 --- a/R/split.r +++ b/R/split.r @@ -41,6 +41,7 @@ #' str_split_n(fruits, " and ", 3) str_split <- function(string, pattern, n = Inf, simplify = FALSE) { if (identical(n, Inf)) n <- -1L + check_lengths(string, pattern) switch(type(pattern), empty = stri_split_boundaries(string, n = n, simplify = simplify, opts_brkiter = opts(pattern)), diff --git a/R/stringr-package.R b/R/stringr-package.R index 97c07e14..0aab7515 100644 --- a/R/stringr-package.R +++ b/R/stringr-package.R @@ -2,6 +2,7 @@ "_PACKAGE" ## usethis namespace: start +#' @import stringi #' @import rlang #' @importFrom glue glue #' @importFrom lifecycle deprecated diff --git a/R/sub.r b/R/sub.r index 2ddea4d7..865b6f6a 100644 --- a/R/sub.r +++ b/R/sub.r @@ -62,6 +62,8 @@ #' str_sub(x4, 1, 2, omit_na = TRUE) <- NA #' x1; x2; x3; x4 str_sub <- function(string, start = 1L, end = -1L) { + vctrs::vec_size_common(string = string, start = start, end = end) + if (is.matrix(start)) { stri_sub(string, from = start) } else { @@ -73,6 +75,8 @@ str_sub <- function(string, start = 1L, end = -1L) { #' @export #' @rdname str_sub "str_sub<-" <- function(string, start = 1L, end = -1L, omit_na = FALSE, value) { + vctrs::vec_size_common(string = string, start = start, end = end) + if (is.matrix(start)) { stri_sub(string, from = start, omit_na = omit_na) <- value } else { diff --git a/R/subset.R b/R/subset.R index ed5ad93c..16c5f625 100644 --- a/R/subset.R +++ b/R/subset.R @@ -30,6 +30,8 @@ #' str_subset(c("a", NA, "b"), ".") #' str_which(c("a", NA, "b"), ".") str_subset <- function(string, pattern, negate = FALSE) { + check_lengths(string, pattern) + switch(type(pattern), empty = , bound = string[str_detect(string, pattern) & !negate], diff --git a/R/trim.R b/R/trim.R index 6022799e..f3ce51e8 100644 --- a/R/trim.R +++ b/R/trim.R @@ -15,7 +15,7 @@ #' str_squish(" String with trailing, middle, and leading white space\t") #' str_squish("\n\nString with excess, trailing and leading white space\n\n") str_trim <- function(string, side = c("both", "left", "right")) { - side <- match.arg(side) + side <- arg_match(side) switch(side, left = stri_trim_left(string), diff --git a/R/trunc.R b/R/trunc.R index d7fe914f..ada0d00d 100644 --- a/R/trunc.R +++ b/R/trunc.R @@ -16,7 +16,10 @@ #' str_trunc <- function(string, width, side = c("right", "left", "center"), ellipsis = "...") { - side <- match.arg(side) + side <- arg_match(side) + if (!is.numeric(width) || length(width) != 1) { + abort("`width` must be a single number") + } too_long <- !is.na(string) & str_length(string) > width width... <- width - str_length(ellipsis) diff --git a/R/utils.R b/R/utils.R index 036dd307..dac04d0e 100644 --- a/R/utils.R +++ b/R/utils.R @@ -7,3 +7,14 @@ #' @importFrom magrittr %>% #' @usage lhs \%>\% rhs NULL + +check_lengths <- function(string, pattern, replacement = NULL) { + # stringi already correctly recycles vectors of length 0 and 1 + # we just want more stringent vctrs checks for other lengths + vctrs::vec_size_common( + string = string, + pattern = pattern, + replacement = replacement + ) +} + diff --git a/R/word.r b/R/word.r index f0de6bfc..d0316c81 100644 --- a/R/word.r +++ b/R/word.r @@ -27,10 +27,10 @@ #' word(str, 1, sep = fixed('..')) #' word(str, 2, sep = fixed('..')) word <- function(string, start = 1L, end = start, sep = fixed(" ")) { - n <- max(length(string), length(start), length(end)) - string <- rep(string, length.out = n) - start <- rep(start, length.out = n) - end <- rep(end, length.out = n) + args <- vctrs::vec_recycle_common(string = string, start = start, end = end) + string <- args$string + start <- args$start + end <- args$end breaks <- str_locate_all(string, sep) words <- lapply(breaks, invert_match) diff --git a/R/wrap.r b/R/wrap.r index 19a7fbd2..14303204 100644 --- a/R/wrap.r +++ b/R/wrap.r @@ -28,6 +28,9 @@ str_wrap <- function(string, indent = 0, exdent = 0, whitespace_only = TRUE) { + if (!is.numeric(width) || length(width) != 1) { + abort("`width` must be a single number") + } if (width <= 0) width <- 1 out <- stri_wrap(string, width = width, indent = indent, exdent = exdent, diff --git a/man/str_c.Rd b/man/str_c.Rd index 289ed0cf..95285554 100644 --- a/man/str_c.Rd +++ b/man/str_c.Rd @@ -7,37 +7,37 @@ str_c(..., sep = "", collapse = NULL) } \arguments{ -\item{...}{One or more character vectors. Zero length arguments -are removed. Short arguments are recycled to the length of the -longest. +\item{...}{One or more character vectors. + +\code{NULL}s are removed; scalar inputs (vectors of length 1) are recycled to +the common length of vector inputs. Like most other R functions, missing values are "infectious": whenever a missing value is combined with another string the result will always -be missing. Use \code{\link[=str_replace_na]{str_replace_na()}} to convert \code{NA} to -\code{"NA"}} +be missing. Use \code{\link[dplyr:coalesce]{dplyr::coalesce()}} or \code{\link[=str_replace_na]{str_replace_na()}} to convert +desired value.} \item{sep}{String to insert between input vectors.} -\item{collapse}{Optional string used to combine input vectors into single -string.} +\item{collapse}{Optional string used to combine output into single +string. Generally better to use \code{\link[=str_flatten]{str_flatten()}} if you needed this +behaviour.} } \value{ If \code{collapse = NULL} (the default) a character vector with -length equal to the longest input string. If \code{collapse} is -non-NULL, a character vector of length 1. +length equal to the longest input. If \code{collapse} is a string, a character +vector of length 1. } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} +\code{str_c()} combines multiple character vectors into a single character +vector. It's very similar to \code{\link[=paste0]{paste0()}} but uses tidyverse recycling and +\code{NA} rules. -\code{str_c()} is no longer needed; please use \code{paste0()} instead. -} -\details{ -To understand how \code{str_c} works, you need to imagine that you are building up -a matrix of strings. Each input argument forms a column, and is expanded to -the length of the longest argument, using the usual recyling rules. The -\code{sep} string is inserted between each column. If collapse is \code{NULL} each row -is collapsed into a single string. If non-\code{NULL} that string is inserted at -the end of each row, and the entire matrix collapsed to a single string. +One way to understand how \code{str_c()} works is picture a 2d matrix of strings, +where each argument forms a column. \code{sep} is inserted between each column, +and then each row is combined together into a single string. If \code{collapse} +is set, it's inserted between each row, and then the result is again +combined, this time into a single string. } \examples{ str_c("Letter: ", letters) @@ -48,13 +48,18 @@ str_c(letters[-26], " comes before ", letters[-1]) str_c(letters, collapse = "") str_c(letters, collapse = ", ") +# Differences from paste() ---------------------- # Missing inputs give missing outputs str_c(c("a", NA, "b"), "-d") +paste0(c("a", NA, "b"), "-d") # Use str_replace_NA to display literal NAs: str_c(str_replace_na(c("a", NA, "b")), "-d") -} -\seealso{ -\code{\link[=paste]{paste()}} for equivalent base R functionality, and -\code{\link[stringi:stri_join]{stringi::stri_join()}} which this function wraps + +# Uses tidyverse recycling rules +\dontrun{str_c(1:2, 1:3)} # errors +paste0(1:2, 1:3) + +str_c("x", character()) +paste0("x", character()) } \keyword{internal} diff --git a/tests/testthat/_snaps/c.md b/tests/testthat/_snaps/c.md new file mode 100644 index 00000000..905b9b38 --- /dev/null +++ b/tests/testthat/_snaps/c.md @@ -0,0 +1,18 @@ +# obeys tidyverse recycling rules + + Code + str_c(c("x", "y"), character()) + Error + Can't recycle `..1` (size 2) to match `..2` (size 0). + +# vectorised arguments error + + Code + str_c(letters, sep = c("a", "b")) + Error + `sep` must be a single string + Code + str_c(letters, collapse = c("a", "b")) + Error + `collapse` must be NULL or single string + diff --git a/tests/testthat/_snaps/detect.md b/tests/testthat/_snaps/detect.md new file mode 100644 index 00000000..2bd56357 --- /dev/null +++ b/tests/testthat/_snaps/detect.md @@ -0,0 +1,19 @@ +# functions use tidyverse recycling rules + + Code + str_detect(1:2, 1:3) + Error + Can't recycle `string` (size 2) to match `pattern` (size 3). + Code + str_starts(1:2, 1:3) + Error + Can't recycle `string` (size 2) to match `pattern` (size 3). + Code + str_ends(1:2, 1:3) + Error + Can't recycle `string` (size 2) to match `pattern` (size 3). + Code + str_like(1:2, c("a", "b", "c")) + Error + Can't recycle `string` (size 2) to match `pattern` (size 3). + diff --git a/tests/testthat/test-c.r b/tests/testthat/test-c.r new file mode 100644 index 00000000..a63d98f6 --- /dev/null +++ b/tests/testthat/test-c.r @@ -0,0 +1,24 @@ +test_that("basic case works", { + test <- c("a", "b", "c") + + expect_equal(str_c(test), test) + expect_equal(str_c(test, sep = " "), test) + expect_equal(str_c(test, collapse = ""), "abc") +}) + +test_that("obeys tidyverse recycling rules", { + expect_equal(str_c(), character()) + + expect_equal(str_c("x", character()), character()) + expect_equal(str_c("x", NULL), "x") + + expect_snapshot(str_c(c("x", "y"), character()), error = TRUE) + expect_equal(str_c(c("x", "y"), NULL), c("x", "y")) +}) + +test_that("vectorised arguments error", { + expect_snapshot(error = TRUE, { + str_c(letters, sep = c("a", "b")) + str_c(letters, collapse = c("a", "b")) + }) +}) diff --git a/tests/testthat/test-conv.R b/tests/testthat/test-conv.R index b5eed416..44b35937 100644 --- a/tests/testthat/test-conv.R +++ b/tests/testthat/test-conv.R @@ -4,3 +4,7 @@ test_that("encoding conversion works", { x <- rawToChar(as.raw(177)) expect_equal(str_conv(x, "latin1"), "±") }) + +test_that("check encoding argument", { + expect_error(str_conv("A", c("ISO-8859-1", "ISO-8859-2")), "single string") +}) diff --git a/tests/testthat/test-count.r b/tests/testthat/test-count.r index d26f2f15..781b0200 100644 --- a/tests/testthat/test-count.r +++ b/tests/testthat/test-count.r @@ -5,3 +5,7 @@ test_that("counts are as expected", { expect_equal(str_count(fruit, "e"), c(1, 0, 1, 2)) expect_equal(str_count(fruit, c("a", "b", "p", "n")), c(1, 1, 1, 1)) }) + +test_that("uses tidyverse recycling rules", { + expect_error(str_count(1:2, 1:3), class = "vctrs_error_incompatible_size") +}) diff --git a/tests/testthat/test-detect.r b/tests/testthat/test-detect.r index ce6db5dd..af156bf2 100644 --- a/tests/testthat/test-detect.r +++ b/tests/testthat/test-detect.r @@ -56,6 +56,15 @@ test_that("str_ends works", { expect_false(str_ends("ab", "c|a")) }) +test_that("functions use tidyverse recycling rules", { + expect_snapshot(error = TRUE, { + str_detect(1:2, 1:3) + str_starts(1:2, 1:3) + str_ends(1:2, 1:3) + str_like(1:2, c("a", "b", "c")) + }) +}) + # str_like ---------------------------------------------------------------- diff --git a/tests/testthat/test-dup.r b/tests/testthat/test-dup.r index b3d41ed9..2872ae37 100644 --- a/tests/testthat/test-dup.r +++ b/tests/testthat/test-dup.r @@ -9,3 +9,7 @@ test_that("0 duplicates equals empty string", { expect_equal(str_dup("a", 0), "") expect_equal(str_dup(c("a", "b"), 0), rep("", 2)) }) + +test_that("uses tidyverse recycling rules", { + expect_error(str_dup(1:2, 1:3), class = "vctrs_error_incompatible_size") +}) diff --git a/tests/testthat/test-extract.r b/tests/testthat/test-extract.r index 4127843e..fec3e0a4 100644 --- a/tests/testthat/test-extract.r +++ b/tests/testthat/test-extract.r @@ -13,6 +13,18 @@ test_that("single pattern extracted correctly", { }) +test_that("uses tidyverse recycling rules", { + expect_error( + str_extract(c("a", "b"), c("a", "b", "c")), + class = "vctrs_error_incompatible_size" + ) + expect_error( + str_extract_all(c("a", "b"), c("a", "b", "c")), + class = "vctrs_error_incompatible_size" + ) +}) + + test_that("no match yields empty vector", { expect_equal(str_extract_all("a", "b")[[1]], character()) }) diff --git a/tests/testthat/test-flatten.R b/tests/testthat/test-flatten.R index 0d816d7a..5e2ec529 100644 --- a/tests/testthat/test-flatten.R +++ b/tests/testthat/test-flatten.R @@ -2,9 +2,12 @@ test_that("equivalent to paste with collapse", { expect_equal(str_flatten(letters), paste0(letters, collapse = "")) }) +test_that("collapse must be single string", { + expect_error(str_flatten("A", c("a", "b")), "single string") +}) + test_that("last optionally used instead of final separator", { expect_equal(str_flatten(letters[1:3], ", ", ", and "), "a, b, and c") expect_equal(str_flatten(letters[1:2], ", ", ", and "), "a, and b") expect_equal(str_flatten(letters[1], ", ", ", and "), "a") - }) diff --git a/tests/testthat/test-join.r b/tests/testthat/test-join.r deleted file mode 100644 index fae1bc85..00000000 --- a/tests/testthat/test-join.r +++ /dev/null @@ -1,14 +0,0 @@ -test_that("basic case works", { - test <- c("a", "b", "c") - - expect_equal(str_c(test), test) - expect_equal(str_c(test, sep = " "), test) - expect_equal(str_c(test, collapse = ""), "abc") -}) - -test_that("NULLs are dropped", { - test <- letters[1:3] - - expect_equal(str_c(test, NULL), test) - expect_equal(str_c(test, NULL, "a", sep = " "), c("a a", "b a", "c a")) -}) diff --git a/tests/testthat/test-locate.r b/tests/testthat/test-locate.r index 2d5e81a2..b429c384 100644 --- a/tests/testthat/test-locate.r +++ b/tests/testthat/test-locate.r @@ -5,6 +5,11 @@ test_that("basic location matching works", { expect_equal(str_locate("abc", ".+")[1, ], c(start = 1, end = 3)) }) +test_that("uses tidyverse recycling rules", { + expect_error(str_locate(1:2, 1:3), class = "vctrs_error_incompatible_size") + expect_error(str_locate_all(1:2, 1:3), class = "vctrs_error_incompatible_size") +}) + test_that("locations are integers", { strings <- c("a b c", "d e f") expect_true(is.integer(str_locate(strings, "[a-z]"))) diff --git a/tests/testthat/test-match.r b/tests/testthat/test-match.r index 605589ae..146a118a 100644 --- a/tests/testthat/test-match.r +++ b/tests/testthat/test-match.r @@ -68,3 +68,14 @@ test_that("match and match_all fail when pattern is not a regex", { expect_error(str_match(phones, fixed("3"))) expect_error(str_match_all(phones, coll("9"))) }) + +test_that("uses tidyverse recycling rules", { + expect_error( + str_match(c("a", "b"), c("a", "b", "c")), + class = "vctrs_error_incompatible_size" + ) + expect_error( + str_match_all(c("a", "b"), c("a", "b", "c")), + class = "vctrs_error_incompatible_size" + ) +}) diff --git a/tests/testthat/test-pad.r b/tests/testthat/test-pad.r index 792e655f..e0af5b09 100644 --- a/tests/testthat/test-pad.r +++ b/tests/testthat/test-pad.r @@ -24,3 +24,14 @@ test_that("padding based of length works", { expect_equal(pad(width = 6), " \u4e2d ") expect_equal(pad(width = 5, use_length = TRUE), " \u4e2d ") }) + +test_that("uses tidyverse recycling rules", { + expect_error( + str_pad(c("a", "b"), 1:3), + class = "vctrs_error_incompatible_size" + ) + expect_error( + str_pad(c("a", "b"), 10, pad = c("a", "b", "c")), + class = "vctrs_error_incompatible_size" + ) +})