From 25959a497b5ccf20462798dd16009ba11d0c8c7a Mon Sep 17 00:00:00 2001 From: stibu81 Date: Thu, 18 Sep 2025 22:19:14 +0200 Subject: [PATCH 1/7] add expect_not_contains() (#1851) --- NAMESPACE | 1 + R/expect-setequal.R | 29 ++++++++++++++++++++++++ man/expect_setequal.Rd | 5 ++++ tests/testthat/_snaps/expect-setequal.md | 22 ++++++++++++++++++ tests/testthat/test-expect-setequal.R | 17 ++++++++++++++ 5 files changed, 74 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 426dbf1b4..677cc9917 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -113,6 +113,7 @@ export(expect_no_match) export(expect_no_message) export(expect_no_success) export(expect_no_warning) +export(expect_not_contains) export(expect_null) export(expect_output) export(expect_output_file) diff --git a/R/expect-setequal.R b/R/expect-setequal.R index 10b864bf2..4ae01a5ef 100644 --- a/R/expect-setequal.R +++ b/R/expect-setequal.R @@ -4,6 +4,8 @@ #' and that every element of `y` occurs in `x`. #' * `expect_contains(x, y)` tests that `x` contains every element of `y` #' (i.e. `y` is a subset of `x`). +#' * `expect_not_contains(x, y)` tests that `x` contains none of the elements +#' of `y` (i.e. `y` is disjoint from `x`). #' * `expect_in(x, y)` tests every element of `x` is in `y` #' (i.e. `x` is a subset of `y`). #' * `expect_mapequal(x, y)` treats lists as if they are mappings between names @@ -117,6 +119,33 @@ expect_contains <- function(object, expected) { pass(act$val) } +#' @export +#' @rdname expect_setequal +expect_not_contains <- function(object, expected) { + act <- quasi_label(enquo(object)) + exp <- quasi_label(enquo(expected)) + + check_vector(object) + check_vector(expected) + + exp_found <- exp$val %in% act$val + if (any(exp_found)) { + msg_exp <- sprintf( + "Expected %s to contain none of the values in %s.", + act$lab, + exp$lab + ) + msg_act <- c( + sprintf("Actual: %s", values(act$val)), + sprintf("Expected: none of %s", values(exp$val)), + sprintf("Found: %s", values(exp$val[exp_found])) + ) + fail(c(msg_exp, msg_act)) + } + + pass(act$val) +} + #' @export #' @rdname expect_setequal expect_in <- function(object, expected) { diff --git a/man/expect_setequal.Rd b/man/expect_setequal.Rd index 41b823a68..e811e499c 100644 --- a/man/expect_setequal.Rd +++ b/man/expect_setequal.Rd @@ -4,6 +4,7 @@ \alias{expect_setequal} \alias{expect_mapequal} \alias{expect_contains} +\alias{expect_not_contains} \alias{expect_in} \title{Do you expect a vector containing these values?} \usage{ @@ -13,6 +14,8 @@ expect_mapequal(object, expected) expect_contains(object, expected) +expect_not_contains(object, expected) + expect_in(object, expected) } \arguments{ @@ -28,6 +31,8 @@ more details.} and that every element of \code{y} occurs in \code{x}. \item \code{expect_contains(x, y)} tests that \code{x} contains every element of \code{y} (i.e. \code{y} is a subset of \code{x}). +\item \code{expect_not_contains(x, y)} tests that \code{x} contains none of the elements +of \code{y} (i.e. \code{y} is disjoint from \code{x}). \item \code{expect_in(x, y)} tests every element of \code{x} is in \code{y} (i.e. \code{x} is a subset of \code{y}). \item \code{expect_mapequal(x, y)} treats lists as if they are mappings between names diff --git a/tests/testthat/_snaps/expect-setequal.md b/tests/testthat/_snaps/expect-setequal.md index d4dad8744..a360e3dc0 100644 --- a/tests/testthat/_snaps/expect-setequal.md +++ b/tests/testthat/_snaps/expect-setequal.md @@ -121,6 +121,28 @@ Expected: "d", "e" Missing: "d", "e" +# expect_not_contains() gives useful message on failure + + Code + expect_not_contains(x1, x2) + Condition + Error: + ! Expected `x1` to contain none of the values in `x2`. + Actual: "a", "b", "c" + Expected: none of "c", "d" + Found: "c" + +--- + + Code + expect_not_contains(x1, x3) + Condition + Error: + ! Expected `x1` to contain none of the values in `x3`. + Actual: "a", "b", "c" + Expected: none of "b", "c", "d" + Found: "b", "c" + # expect_in() gives useful message on failure Code diff --git a/tests/testthat/test-expect-setequal.R b/tests/testthat/test-expect-setequal.R index 45b031a04..a9574cdae 100644 --- a/tests/testthat/test-expect-setequal.R +++ b/tests/testthat/test-expect-setequal.R @@ -131,6 +131,23 @@ test_that("expect_contains() gives useful message on failure", { expect_snapshot_failure(expect_contains(x1, x3)) }) +# not_contains ---------------------------------------------------------------- + +test_that("expect_not_contains() succeeds when appropriate", { + expect_success(expect_not_contains(letters, 1)) + expect_success(expect_not_contains(letters, LETTERS)) + expect_success(expect_not_contains(letters, character())) +}) + +test_that("expect_not_contains() gives useful message on failure", { + x1 <- c("a", "b", "c") + x2 <- c("c", "d") + x3 <- c("b", "c", "d") + + expect_snapshot_failure(expect_not_contains(x1, x2)) + expect_snapshot_failure(expect_not_contains(x1, x3)) +}) + # in ---------------------------------------------------------------- From 0d18c576bed60d2098c16c8e0df32073eeb1ad10 Mon Sep 17 00:00:00 2001 From: stibu81 Date: Thu, 18 Sep 2025 22:32:44 +0200 Subject: [PATCH 2/7] add expect_not_in() (#1851) --- NAMESPACE | 1 + R/expect-setequal.R | 31 +++++++++++++++++++++++- man/expect_setequal.Rd | 7 +++++- tests/testthat/_snaps/expect-setequal.md | 22 +++++++++++++++++ tests/testthat/test-expect-setequal.R | 17 +++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 677cc9917..4a82b886d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -114,6 +114,7 @@ export(expect_no_message) export(expect_no_success) export(expect_no_warning) export(expect_not_contains) +export(expect_not_in) export(expect_null) export(expect_output) export(expect_output_file) diff --git a/R/expect-setequal.R b/R/expect-setequal.R index 4ae01a5ef..0a712172f 100644 --- a/R/expect-setequal.R +++ b/R/expect-setequal.R @@ -6,8 +6,10 @@ #' (i.e. `y` is a subset of `x`). #' * `expect_not_contains(x, y)` tests that `x` contains none of the elements #' of `y` (i.e. `y` is disjoint from `x`). -#' * `expect_in(x, y)` tests every element of `x` is in `y` +#' * `expect_in(x, y)` tests that every element of `x` is in `y` #' (i.e. `x` is a subset of `y`). +#' * `expect_not_in(x, y)` tests that no element of `x` is in `y` +#' (i.e. `x` is disjoint from `y`). #' * `expect_mapequal(x, y)` treats lists as if they are mappings between names #' and values. Concretely, this drops `NULL`s in both objects and sorts #' named components. @@ -173,6 +175,33 @@ expect_in <- function(object, expected) { pass(act$val) } +#' @export +#' @rdname expect_setequal +expect_not_in <- function(object, expected) { + act <- quasi_label(enquo(object)) + exp <- quasi_label(enquo(expected)) + + check_vector(object) + check_vector(expected) + + act_found <- act$val %in% exp$val + if (any(act_found)) { + msg_exp <- sprintf( + "Expected %s to contain no values from %s.", + act$lab, + exp$lab + ) + msg_act <- c( + sprintf("Actual: %s", values(act$val)), + sprintf("Expected: none of %s", values(exp$val)), + sprintf("Invalid: %s", values(act$val[act_found])) + ) + fail(c(msg_exp, msg_act)) + } + + pass(act$val) +} + # Helpers ---------------------------------------------------------------------- check_vector <- function( diff --git a/man/expect_setequal.Rd b/man/expect_setequal.Rd index e811e499c..0136b2bea 100644 --- a/man/expect_setequal.Rd +++ b/man/expect_setequal.Rd @@ -6,6 +6,7 @@ \alias{expect_contains} \alias{expect_not_contains} \alias{expect_in} +\alias{expect_not_in} \title{Do you expect a vector containing these values?} \usage{ expect_setequal(object, expected) @@ -17,6 +18,8 @@ expect_contains(object, expected) expect_not_contains(object, expected) expect_in(object, expected) + +expect_not_in(object, expected) } \arguments{ \item{object, expected}{Computation and value to compare it to. @@ -33,8 +36,10 @@ and that every element of \code{y} occurs in \code{x}. (i.e. \code{y} is a subset of \code{x}). \item \code{expect_not_contains(x, y)} tests that \code{x} contains none of the elements of \code{y} (i.e. \code{y} is disjoint from \code{x}). -\item \code{expect_in(x, y)} tests every element of \code{x} is in \code{y} +\item \code{expect_in(x, y)} tests that every element of \code{x} is in \code{y} (i.e. \code{x} is a subset of \code{y}). +\item \code{expect_not_in(x, y)} tests that no element of \code{x} is in \code{y} +(i.e. \code{x} is disjoint from \code{y}). \item \code{expect_mapequal(x, y)} treats lists as if they are mappings between names and values. Concretely, this drops \code{NULL}s in both objects and sorts named components. diff --git a/tests/testthat/_snaps/expect-setequal.md b/tests/testthat/_snaps/expect-setequal.md index a360e3dc0..c71b2008f 100644 --- a/tests/testthat/_snaps/expect-setequal.md +++ b/tests/testthat/_snaps/expect-setequal.md @@ -165,3 +165,25 @@ Expected: "d", "e" Invalid: "a", "b" +# expect_not_in() gives useful message on failure + + Code + expect_not_in(x1, x2) + Condition + Error: + ! Expected `x1` to contain no values from `x2`. + Actual: "a", "b", "c" + Expected: none of "c", "d" + Invalid: "c" + +--- + + Code + expect_not_in(x1, x3) + Condition + Error: + ! Expected `x1` to contain no values from `x3`. + Actual: "a", "b", "c" + Expected: none of "b", "c", "d" + Invalid: "b", "c" + diff --git a/tests/testthat/test-expect-setequal.R b/tests/testthat/test-expect-setequal.R index a9574cdae..8f2291aad 100644 --- a/tests/testthat/test-expect-setequal.R +++ b/tests/testthat/test-expect-setequal.R @@ -165,3 +165,20 @@ test_that("expect_in() gives useful message on failure", { expect_snapshot_failure(expect_in(x1, x2)) expect_snapshot_failure(expect_in(x1, x3)) }) + +# not_in ---------------------------------------------------------------- + +test_that("expect_not_in() succeeds when appropriate", { + expect_success(expect_not_in(1, letters)) + expect_success(expect_not_in(LETTERS, letters)) + expect_success(expect_not_in(character(), letters)) +}) + +test_that("expect_not_in() gives useful message on failure", { + x1 <- c("a", "b", "c") + x2 <- c("c", "d") + x3 <- c("b", "c", "d") + + expect_snapshot_failure(expect_not_in(x1, x2)) + expect_snapshot_failure(expect_not_in(x1, x3)) +}) From 86ef654b86c8653d2fea1cd560c149084882a170 Mon Sep 17 00:00:00 2001 From: stibu81 Date: Thu, 18 Sep 2025 22:40:31 +0200 Subject: [PATCH 3/7] update news --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index a9ddc93db..3584f15ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # testthat (development version) +* New `expect_not_contains()` and `expect_not_in()` to check for the absence of values (#1851). * New `vignette("mocking")` explains mocking in detail (#1265). * New `vignette("challenging-functions")` provides an index to other documentation organised by testing challenges (#1265). * When running a test interactively, testthat now reports the number of succeses. The results should also be more useful if you are using nested tests. From eb9eca6e6318d8d1d4b474738f59631614db7cd2 Mon Sep 17 00:00:00 2001 From: stibu81 Date: Fri, 10 Oct 2025 17:32:21 +0200 Subject: [PATCH 4/7] replace expect_not_in() and expect_not_contains() by expect_disjoint() --- NAMESPACE | 3 +- NEWS.md | 2 +- R/expect-setequal.R | 40 ++++-------------------- man/expect_setequal.Rd | 11 ++----- tests/testthat/_snaps/expect-setequal.md | 32 +++---------------- tests/testthat/test-expect-setequal.R | 33 +++++-------------- 6 files changed, 24 insertions(+), 97 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 4a82b886d..54d01216c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -82,6 +82,7 @@ export(expect) export(expect_condition) export(expect_contains) export(expect_cpp_tests_pass) +export(expect_disjoint) export(expect_equal) export(expect_equal_to_reference) export(expect_equivalent) @@ -113,8 +114,6 @@ export(expect_no_match) export(expect_no_message) export(expect_no_success) export(expect_no_warning) -export(expect_not_contains) -export(expect_not_in) export(expect_null) export(expect_output) export(expect_output_file) diff --git a/NEWS.md b/NEWS.md index 3584f15ae..a88a3beae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # testthat (development version) -* New `expect_not_contains()` and `expect_not_in()` to check for the absence of values (#1851). +* New `expect_disjoint()` to check for the absence of values (#1851). * New `vignette("mocking")` explains mocking in detail (#1265). * New `vignette("challenging-functions")` provides an index to other documentation organised by testing challenges (#1265). * When running a test interactively, testthat now reports the number of succeses. The results should also be more useful if you are using nested tests. diff --git a/R/expect-setequal.R b/R/expect-setequal.R index 0a712172f..cf5ecdfe7 100644 --- a/R/expect-setequal.R +++ b/R/expect-setequal.R @@ -4,11 +4,9 @@ #' and that every element of `y` occurs in `x`. #' * `expect_contains(x, y)` tests that `x` contains every element of `y` #' (i.e. `y` is a subset of `x`). -#' * `expect_not_contains(x, y)` tests that `x` contains none of the elements -#' of `y` (i.e. `y` is disjoint from `x`). #' * `expect_in(x, y)` tests that every element of `x` is in `y` #' (i.e. `x` is a subset of `y`). -#' * `expect_not_in(x, y)` tests that no element of `x` is in `y` +#' * `expect_disjoint(x, y)` tests that no element of `x` is in `y` #' (i.e. `x` is disjoint from `y`). #' * `expect_mapequal(x, y)` treats lists as if they are mappings between names #' and values. Concretely, this drops `NULL`s in both objects and sorts @@ -121,32 +119,6 @@ expect_contains <- function(object, expected) { pass(act$val) } -#' @export -#' @rdname expect_setequal -expect_not_contains <- function(object, expected) { - act <- quasi_label(enquo(object)) - exp <- quasi_label(enquo(expected)) - - check_vector(object) - check_vector(expected) - - exp_found <- exp$val %in% act$val - if (any(exp_found)) { - msg_exp <- sprintf( - "Expected %s to contain none of the values in %s.", - act$lab, - exp$lab - ) - msg_act <- c( - sprintf("Actual: %s", values(act$val)), - sprintf("Expected: none of %s", values(exp$val)), - sprintf("Found: %s", values(exp$val[exp_found])) - ) - fail(c(msg_exp, msg_act)) - } - - pass(act$val) -} #' @export #' @rdname expect_setequal @@ -177,24 +149,24 @@ expect_in <- function(object, expected) { #' @export #' @rdname expect_setequal -expect_not_in <- function(object, expected) { +expect_disjoint <- function(object, expected) { act <- quasi_label(enquo(object)) exp <- quasi_label(enquo(expected)) check_vector(object) check_vector(expected) - act_found <- act$val %in% exp$val - if (any(act_found)) { + act_common <- act$val %in% exp$val + if (any(act_common)) { msg_exp <- sprintf( - "Expected %s to contain no values from %s.", + "Expected %s to be disjoint from %s.", act$lab, exp$lab ) msg_act <- c( sprintf("Actual: %s", values(act$val)), sprintf("Expected: none of %s", values(exp$val)), - sprintf("Invalid: %s", values(act$val[act_found])) + sprintf("Invalid: %s", values(act$val[act_common])) ) fail(c(msg_exp, msg_act)) } diff --git a/man/expect_setequal.Rd b/man/expect_setequal.Rd index 0136b2bea..0ebd92b46 100644 --- a/man/expect_setequal.Rd +++ b/man/expect_setequal.Rd @@ -4,9 +4,8 @@ \alias{expect_setequal} \alias{expect_mapequal} \alias{expect_contains} -\alias{expect_not_contains} \alias{expect_in} -\alias{expect_not_in} +\alias{expect_disjoint} \title{Do you expect a vector containing these values?} \usage{ expect_setequal(object, expected) @@ -15,11 +14,9 @@ expect_mapequal(object, expected) expect_contains(object, expected) -expect_not_contains(object, expected) - expect_in(object, expected) -expect_not_in(object, expected) +expect_disjoint(object, expected) } \arguments{ \item{object, expected}{Computation and value to compare it to. @@ -34,11 +31,9 @@ more details.} and that every element of \code{y} occurs in \code{x}. \item \code{expect_contains(x, y)} tests that \code{x} contains every element of \code{y} (i.e. \code{y} is a subset of \code{x}). -\item \code{expect_not_contains(x, y)} tests that \code{x} contains none of the elements -of \code{y} (i.e. \code{y} is disjoint from \code{x}). \item \code{expect_in(x, y)} tests that every element of \code{x} is in \code{y} (i.e. \code{x} is a subset of \code{y}). -\item \code{expect_not_in(x, y)} tests that no element of \code{x} is in \code{y} +\item \code{expect_disjoint(x, y)} tests that no element of \code{x} is in \code{y} (i.e. \code{x} is disjoint from \code{y}). \item \code{expect_mapequal(x, y)} treats lists as if they are mappings between names and values. Concretely, this drops \code{NULL}s in both objects and sorts diff --git a/tests/testthat/_snaps/expect-setequal.md b/tests/testthat/_snaps/expect-setequal.md index c71b2008f..49a73a7b7 100644 --- a/tests/testthat/_snaps/expect-setequal.md +++ b/tests/testthat/_snaps/expect-setequal.md @@ -121,28 +121,6 @@ Expected: "d", "e" Missing: "d", "e" -# expect_not_contains() gives useful message on failure - - Code - expect_not_contains(x1, x2) - Condition - Error: - ! Expected `x1` to contain none of the values in `x2`. - Actual: "a", "b", "c" - Expected: none of "c", "d" - Found: "c" - ---- - - Code - expect_not_contains(x1, x3) - Condition - Error: - ! Expected `x1` to contain none of the values in `x3`. - Actual: "a", "b", "c" - Expected: none of "b", "c", "d" - Found: "b", "c" - # expect_in() gives useful message on failure Code @@ -165,13 +143,13 @@ Expected: "d", "e" Invalid: "a", "b" -# expect_not_in() gives useful message on failure +# expect_disjoint() gives useful message on failure Code - expect_not_in(x1, x2) + expect_disjoint(x1, x2) Condition Error: - ! Expected `x1` to contain no values from `x2`. + ! Expected `x1` to be disjoint from `x2`. Actual: "a", "b", "c" Expected: none of "c", "d" Invalid: "c" @@ -179,10 +157,10 @@ --- Code - expect_not_in(x1, x3) + expect_disjoint(x1, x3) Condition Error: - ! Expected `x1` to contain no values from `x3`. + ! Expected `x1` to be disjoint from `x3`. Actual: "a", "b", "c" Expected: none of "b", "c", "d" Invalid: "b", "c" diff --git a/tests/testthat/test-expect-setequal.R b/tests/testthat/test-expect-setequal.R index 8f2291aad..ea60894e0 100644 --- a/tests/testthat/test-expect-setequal.R +++ b/tests/testthat/test-expect-setequal.R @@ -131,23 +131,6 @@ test_that("expect_contains() gives useful message on failure", { expect_snapshot_failure(expect_contains(x1, x3)) }) -# not_contains ---------------------------------------------------------------- - -test_that("expect_not_contains() succeeds when appropriate", { - expect_success(expect_not_contains(letters, 1)) - expect_success(expect_not_contains(letters, LETTERS)) - expect_success(expect_not_contains(letters, character())) -}) - -test_that("expect_not_contains() gives useful message on failure", { - x1 <- c("a", "b", "c") - x2 <- c("c", "d") - x3 <- c("b", "c", "d") - - expect_snapshot_failure(expect_not_contains(x1, x2)) - expect_snapshot_failure(expect_not_contains(x1, x3)) -}) - # in ---------------------------------------------------------------- @@ -166,19 +149,19 @@ test_that("expect_in() gives useful message on failure", { expect_snapshot_failure(expect_in(x1, x3)) }) -# not_in ---------------------------------------------------------------- +# disjoint ---------------------------------------------------------------- -test_that("expect_not_in() succeeds when appropriate", { - expect_success(expect_not_in(1, letters)) - expect_success(expect_not_in(LETTERS, letters)) - expect_success(expect_not_in(character(), letters)) +test_that("expect_disjoint() succeeds when appropriate", { + expect_success(expect_disjoint(1, letters)) + expect_success(expect_disjoint(LETTERS, letters)) + expect_success(expect_disjoint(character(), letters)) }) -test_that("expect_not_in() gives useful message on failure", { +test_that("expect_disjoint() gives useful message on failure", { x1 <- c("a", "b", "c") x2 <- c("c", "d") x3 <- c("b", "c", "d") - expect_snapshot_failure(expect_not_in(x1, x2)) - expect_snapshot_failure(expect_not_in(x1, x3)) + expect_snapshot_failure(expect_disjoint(x1, x2)) + expect_snapshot_failure(expect_disjoint(x1, x3)) }) From 100e4e03d87fbaef8de5f94dc73b02b2fede7585 Mon Sep 17 00:00:00 2001 From: stibu81 Date: Fri, 10 Oct 2025 20:41:35 +0200 Subject: [PATCH 5/7] resolve review comments from @DavisVaughan --- R/expect-setequal.R | 2 +- tests/testthat/_snaps/expect-setequal.md | 15 +++++++++++++-- tests/testthat/test-expect-setequal.R | 1 + 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/R/expect-setequal.R b/R/expect-setequal.R index cf5ecdfe7..1db17c2d4 100644 --- a/R/expect-setequal.R +++ b/R/expect-setequal.R @@ -165,7 +165,7 @@ expect_disjoint <- function(object, expected) { ) msg_act <- c( sprintf("Actual: %s", values(act$val)), - sprintf("Expected: none of %s", values(exp$val)), + sprintf("Expected: None of %s", values(exp$val)), sprintf("Invalid: %s", values(act$val[act_common])) ) fail(c(msg_exp, msg_act)) diff --git a/tests/testthat/_snaps/expect-setequal.md b/tests/testthat/_snaps/expect-setequal.md index 49a73a7b7..19943abb5 100644 --- a/tests/testthat/_snaps/expect-setequal.md +++ b/tests/testthat/_snaps/expect-setequal.md @@ -151,7 +151,7 @@ Error: ! Expected `x1` to be disjoint from `x2`. Actual: "a", "b", "c" - Expected: none of "c", "d" + Expected: None of "c", "d" Invalid: "c" --- @@ -162,6 +162,17 @@ Error: ! Expected `x1` to be disjoint from `x3`. Actual: "a", "b", "c" - Expected: none of "b", "c", "d" + Expected: None of "b", "c", "d" Invalid: "b", "c" +--- + + Code + expect_disjoint(NA, c("a", NA)) + Condition + Error: + ! Expected NA to be disjoint from `c("a", NA)`. + Actual: NA + Expected: None of "a", NA + Invalid: NA + diff --git a/tests/testthat/test-expect-setequal.R b/tests/testthat/test-expect-setequal.R index ea60894e0..a7f0f932f 100644 --- a/tests/testthat/test-expect-setequal.R +++ b/tests/testthat/test-expect-setequal.R @@ -164,4 +164,5 @@ test_that("expect_disjoint() gives useful message on failure", { expect_snapshot_failure(expect_disjoint(x1, x2)) expect_snapshot_failure(expect_disjoint(x1, x3)) + expect_snapshot_failure(expect_disjoint(NA, c("a", NA))) }) From 2f5a8a492a70b64ccf338d95983c7a691a852452 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Fri, 10 Oct 2025 13:47:52 -0500 Subject: [PATCH 6/7] Update expectation style --- R/expect-setequal.R | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/R/expect-setequal.R b/R/expect-setequal.R index dd96db3f8..2204ec140 100644 --- a/R/expect-setequal.R +++ b/R/expect-setequal.R @@ -183,25 +183,22 @@ expect_disjoint <- function(object, expected) { act <- quasi_label(enquo(object)) exp <- quasi_label(enquo(expected)) - check_vector(object) - check_vector(expected) - + check_vector(act$val) + check_vector(exp$val) act_common <- act$val %in% exp$val + if (any(act_common)) { - msg_exp <- sprintf( - "Expected %s to be disjoint from %s.", - act$lab, - exp$lab - ) - msg_act <- c( + fail(c( + sprintf("Expected %s to be disjoint from %s.", act$lab, exp$lab), sprintf("Actual: %s", values(act$val)), sprintf("Expected: None of %s", values(exp$val)), sprintf("Invalid: %s", values(act$val[act_common])) - ) - fail(c(msg_exp, msg_act)) + )) + } else { + pass() } - pass(act$val) + invisible(act$val) } # Helpers ---------------------------------------------------------------------- From e73be517679dbec1dd235a499cacc9ccdadddfab Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Fri, 10 Oct 2025 13:49:58 -0500 Subject: [PATCH 7/7] More style tweaks --- R/expect-setequal.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/expect-setequal.R b/R/expect-setequal.R index 2204ec140..c9fe45649 100644 --- a/R/expect-setequal.R +++ b/R/expect-setequal.R @@ -183,10 +183,10 @@ expect_disjoint <- function(object, expected) { act <- quasi_label(enquo(object)) exp <- quasi_label(enquo(expected)) - check_vector(act$val) - check_vector(exp$val) - act_common <- act$val %in% exp$val + check_vector(act$val, error_arg = "object") + check_vector(exp$val, error_arg = "expected") + act_common <- act$val %in% exp$val if (any(act_common)) { fail(c( sprintf("Expected %s to be disjoint from %s.", act$lab, exp$lab),