Skip to content

Commit f04808b

Browse files
committed
Merge commit 'e4b8747b316288a1aa148a2df813dc2516ee90e8'
2 parents 34ea6f4 + e4b8747 commit f04808b

File tree

161 files changed

+8457
-1546
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+8457
-1546
lines changed

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"Bash(find:*)",
66
"Bash(R:*)",
77
"Bash(rm:*)",
8-
"Bash(air format:*)"
8+
"Bash(air format:*)",
9+
"Edit(R/**)",
10+
"Edit(tests/**)",
11+
"Edit(vignettes/**)"
912
],
1013
"deny": []
1114
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Claude Code Review
2+
3+
on:
4+
pull_request:
5+
types: [opened]
6+
# Optional: Only run on specific file changes
7+
# paths:
8+
# - "src/**/*.ts"
9+
# - "src/**/*.tsx"
10+
# - "src/**/*.js"
11+
# - "src/**/*.jsx"
12+
13+
jobs:
14+
claude-review:
15+
# Optional: Filter by PR author
16+
# if: |
17+
# github.event.pull_request.user.login == 'external-contributor' ||
18+
# github.event.pull_request.user.login == 'new-developer' ||
19+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20+
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
pull-requests: read
25+
issues: read
26+
id-token: write
27+
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 1
33+
34+
- name: Run Claude Code Review
35+
id: claude-review
36+
uses: anthropics/claude-code-action@beta
37+
with:
38+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
39+
40+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
41+
# model: "claude-opus-4-1-20250805"
42+
43+
# Direct prompt for automated review (no @claude mention needed)
44+
direct_prompt: |
45+
Please review this pull request and MINIMAL provide feedback on
46+
potential bugs or issues. Never praise or flatter. Be concise.
47+
Use simple, clear language. If you don't have anything significant
48+
to add, just reply LGTM.
49+
50+
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
51+
# use_sticky_comment: true
52+
53+
# Optional: Add specific tools for running tests or linting
54+
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
55+
56+
# Optional: Skip review for certain conditions
57+
# if: |
58+
# !contains(github.event.pull_request.title, '[skip-review]') &&
59+
# !contains(github.event.pull_request.title, '[WIP]')

.github/workflows/claude.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Claude Code
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
issues:
9+
types: [opened, assigned]
10+
pull_request_review:
11+
types: [submitted]
12+
13+
jobs:
14+
claude:
15+
if: |
16+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
pull-requests: read
24+
issues: read
25+
id-token: write
26+
actions: read # Required for Claude to read CI results on PRs
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 1
32+
33+
- name: Install air
34+
uses: posit-dev/setup-air@v1
35+
36+
- uses: r-lib/actions/setup-r@v2
37+
with:
38+
use-public-rspm: true
39+
40+
- uses: r-lib/actions/setup-r-dependencies@v2
41+
with:
42+
extra-packages: any::rcmdcheck,roxygen2
43+
needs: check
44+
45+
- name: Run Claude Code
46+
id: claude
47+
uses: anthropics/claude-code-action@beta
48+
with:
49+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
50+
51+
# This is an optional setting that allows Claude to read CI results on PRs
52+
additional_permissions: |
53+
actions: read
54+
55+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
56+
# model: "claude-opus-4-1-20250805"
57+
58+
# Optional: Customize the trigger phrase (default: @claude)
59+
# trigger_phrase: "/claude"
60+
61+
# Optional: Trigger when specific user is assigned to an issue
62+
# assignee_trigger: "claude-bot"
63+
64+
# Optional: Allow Claude to run specific commands
65+
allowed_tools: "Bash(R:*);Bash(air format:*)"
66+
67+
# Optional: Add custom instructions for Claude to customize its behavior for your project
68+
# custom_instructions: |
69+
# Follow our coding standards
70+
# Ensure all new code has tests
71+
# Use TypeScript for new files
72+
73+
# Optional: Custom environment variables for Claude
74+
# claude_env: |
75+
# NODE_ENV: test

CLAUDE.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,23 @@ General advice:
1212
* When running R from the console, always run it with `--quiet --vanilla`
1313
* Always run `air format .` after generating code
1414

15-
### Development tools
15+
### Testing
1616

17-
- `devtools::test()` - Run all tests
18-
- `devtools::test_file("tests/testthat/test-filename.R")` - Run tests in a specific file
17+
- Use `devtools::test()` to run all tests
18+
- Use `devtools::test_file("tests/testthat/test-filename.R")` to run tests in a specific file
1919
- DO NOT USE `devtools::test_active_file()`
20-
- `devtools::load_all()` - Load package for development
21-
- `devtools::check()` - Run R CMD check
22-
- `devtools::install()` - Install package locally
20+
- All testing functions automatically load code; you don't needs to.
21+
22+
- All new code should have an accompanying test.
23+
- Tests for `R/{name}.R` go in `tests/testthat/test-{name}.R`.
24+
- If there are existing tests, place new tests next to similar existing tests.
2325

2426
### Documentation
2527

2628
- Always run `devtools::document()` after changing any roxygen2 docs.
29+
- Every user facing function should be exported and have roxygen2 documentation.
30+
- Whenever you add a new documentation file, make sure to also add the topic name to `_pkgdown.yml`.
31+
- Run `pkgdown::check_pkgdown()` to check that all topics are included in the reference index.
2732

2833
## Core Architecture
2934

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Suggests:
4040
curl (>= 0.9.5),
4141
diffviewer (>= 0.1.0),
4242
digest (>= 0.6.33),
43+
gh,
4344
knitr,
4445
rmarkdown,
4546
rstudioapi,

NAMESPACE

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ export(it)
158158
export(local_edition)
159159
export(local_mock)
160160
export(local_mocked_bindings)
161+
export(local_mocked_r6_class)
162+
export(local_mocked_s3_method)
163+
export(local_mocked_s4_method)
161164
export(local_on_cran)
162165
export(local_reproducible_output)
163166
export(local_snapshotter)
@@ -192,6 +195,7 @@ export(skip_on_os)
192195
export(skip_on_travis)
193196
export(skip_unless_r)
194197
export(snapshot_accept)
198+
export(snapshot_download_gh)
195199
export(snapshot_reject)
196200
export(snapshot_review)
197201
export(source_dir)
@@ -233,4 +237,5 @@ importFrom(brio,readLines)
233237
importFrom(brio,writeLines)
234238
importFrom(lifecycle,deprecated)
235239
importFrom(magrittr,"%>%")
240+
importFrom(methods,new)
236241
useDynLib(testthat, .registration = TRUE)

NEWS.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# testthat (development version)
22

3+
* `expect_snapshot_file()` now considers `.json` to be a text file (#1593).
4+
* `expect_snapshot_file()` now shows differences for text files (#1593).
5+
* The failure messages for all `expect_` functions have been rewritten to first state what was expected and then what was actually received (#2142).
6+
* `test_file(desc = ...)` no longer loses snapshot results (#2066).
7+
* In `R CMD check`, snapshots now only advise on how to resolve failures once (#2207).
8+
* `snapshot_review()` includes a reject button and only displays the file navigation and the skip button if there are multiple files to review (#2025).
9+
* New `snapshot_download_gh()` makes it easy to get snapshots off GitHub and into your local package (#1779).
10+
* New `local_mocked_s3_method()`, `local_mocked_s4_method()`, and `local_mocked_r6_class()` allow you to mock S3 and S4 methods and R6 classes (#1892, #1916)
11+
* `expect_snapshot_file(name=)` must have a unique file path. If a snapshot file attempts to be saved with a duplicate `name`, an error will be thrown. (#1592)
12+
* `test_dir()`, `test_file()`, `test_package()`, `test_check()`, `test_local()`, `source_file()` gain a `shuffle` argument uses `sample()` to randomly reorder the top-level expressions in each test file (#1942). This random reordering surfaces dependencies between tests and code outside of any test, as well as dependencies between tests. This helps you find and eliminate unintentional dependencies.
13+
* `snapshot_accept(test)` now works when the test file name contains `.` (#1669).
14+
* `local_mock()` and `with_mock()` have been deprecated because they are no longer permitted in R 4.5.
15+
* `snapshot_review()` now passes `...` on to `shiny::runApp()` (#1928).
16+
* `expect_named()` now gives more informative errors (#2091).
17+
* `expect_*()` functions consistently and rigorously check their inputs (#1754).
318
* `test_that()` no longer warns about the absence of `{}` since it no longer seems to be necessary.
419
* `test_that()`, `describe()`, and `it()` can now be arbitrarily nested. Each component will skip only if it and its subtests don't contain any expectations. The interactive stop reporter has been fixed so it doesn't duplicate failures. (#2063, #2188).
520
* Test filtering now works with `it()`, and the `desc` argument can take a character vector in order to recursively filter subtests (i.e. `it()` nested inside of `describe()`) (#2118).
@@ -37,7 +52,7 @@
3752
* Fixed an issue preventing compilation from succeeding due to deprecation / removal of `std::uncaught_exception()` (@kevinushey, #2047).
3853
* New `skip_unless_r()` to skip running tests on unsuitable versions of R, e.g. `skip_unless_r(">= 4.1.0")` to skip tests that require, say, `...names` (@MichaelChirico, #2022)
3954
* `skip_on_os()` gains an option `"emscripten"` of the `os` argument to skip tests on Emscripten (@eitsupi, #2103).
40-
* New expectation, `expect_shape()`, for testing the shape (i.e., the `length()`, `nrow()`, `ncol()`, or `dim()`), all in one place (#1423, @michaelchirico).
55+
* New expectation, `expect_shape()`, for testing the shape (i.e., the `nrow()`, `ncol()`, or `dim()`), all in one place (#1423, @michaelchirico).
4156

4257
# testthat 3.2.3
4358

R/auto-test.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ auto_test_package <- function(
8383
test_path <- normalizePath(file.path(path, "tests", "testthat"))
8484

8585
# Start by loading all code and running all tests
86-
withr::local_envvar("NOT_CRAN" = "true")
86+
local_assume_not_on_cran()
8787
pkgload::load_all(path)
8888
test_dir(
8989
test_path,

R/expect-comparison.R

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,7 @@ expect_compare_ <- function(
2828
operator <- match.arg(operator)
2929
op <- match.fun(operator)
3030

31-
msg <- c(
32-
"<" = "not strictly less than",
33-
"<=" = "not less than",
34-
">" = "not strictly greater than",
35-
">=" = "not greater than"
36-
)[[operator]]
37-
38-
negated_op <- switch(operator, "<" = ">=", "<=" = ">", ">" = "<=", ">=" = "<")
31+
actual_op <- switch(operator, "<" = ">=", "<=" = ">", ">" = "<=", ">=" = "<")
3932

4033
cmp <- op(act$val, exp$val)
4134
if (length(cmp) != 1 || !is.logical(cmp)) {
@@ -45,22 +38,32 @@ expect_compare_ <- function(
4538
)
4639
}
4740
if (!isTRUE(cmp)) {
41+
diff <- act$val - exp$val
42+
msg_exp <- sprintf("Expected %s %s %s.", act$lab, operator, exp$lab)
43+
4844
digits <- max(
4945
digits(act$val),
5046
digits(exp$val),
5147
min_digits(act$val, exp$val)
5248
)
53-
msg <- sprintf(
54-
"%s is %s %s.\n%s - %s = %s %s 0",
55-
act$lab,
56-
msg,
57-
exp$lab,
49+
50+
msg_act <- sprintf(
51+
"Actual comparison: %s %s %s",
5852
num_exact(act$val, digits),
59-
num_exact(exp$val, digits),
60-
num_exact(act$val - exp$val, digits),
61-
negated_op
53+
actual_op,
54+
num_exact(exp$val, digits)
6255
)
63-
return(fail(msg, trace_env = trace_env))
56+
57+
if (is.na(diff)) {
58+
msg_diff <- NULL
59+
} else {
60+
msg_diff <- sprintf(
61+
"Difference: %s %s 0",
62+
num_exact(diff, digits),
63+
actual_op
64+
)
65+
}
66+
return(fail(c(msg_exp, msg_act, msg_diff), trace_env = trace_env))
6467
}
6568
pass(act$val)
6669
}

R/expect-condition.R

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,10 @@ compare_condition_3e <- function(cond_type, cond_class, cond, lab, expected) {
440440
if (expected) {
441441
if (is.null(cond)) {
442442
if (is.null(cond_class)) {
443-
sprintf("%s did not throw the expected %s.", lab, cond_type)
443+
sprintf("Expected %s to throw a %s.", lab, cond_type)
444444
} else {
445445
sprintf(
446-
"%s did not throw a %s with class <%s>.",
446+
"Expected %s to throw a %s with class <%s>.",
447447
lab,
448448
cond_type,
449449
cond_class
@@ -454,12 +454,9 @@ compare_condition_3e <- function(cond_type, cond_class, cond, lab, expected) {
454454
}
455455
} else {
456456
if (!is.null(cond)) {
457-
sprintf(
458-
"%s threw an unexpected %s.\nMessage: %s\nClass: %s",
459-
lab,
460-
cond_type,
461-
cnd_message(cond),
462-
paste(class(cond), collapse = "/")
457+
c(
458+
sprintf("Expected %s not to throw any %ss.", lab, cond_type),
459+
actual_condition(cond)
463460
)
464461
} else {
465462
NULL
@@ -493,7 +490,7 @@ compare_condition_2e <- function(
493490

494491
# Otherwise we're definitely expecting a condition
495492
if (is.null(cond)) {
496-
return(sprintf("%s did not throw an %s.", lab, cond_type))
493+
return(sprintf("Expected %s to throw a %s.", lab, cond_type))
497494
}
498495

499496
matches <- cnd_matches_2e(cond, class, regexp, inherit, ...)
@@ -562,15 +559,20 @@ compare_messages <- function(
562559
# Expecting no messages
563560
if (identical(regexp, NA)) {
564561
if (length(messages) > 0) {
565-
return(sprintf("%s generated %s:\n%s", lab, cond_type, bullets))
562+
return(sprintf(
563+
"Expected %s not to generate %s.\nActually generated:\n%s",
564+
lab,
565+
cond_type,
566+
bullets
567+
))
566568
} else {
567569
return()
568570
}
569571
}
570572

571573
# Otherwise we're definitely expecting messages
572574
if (length(messages) == 0) {
573-
return(sprintf("%s did not produce any %s.", lab, cond_type))
575+
return(sprintf("Expected %s to produce %s.", lab, cond_type))
574576
}
575577

576578
if (is.null(regexp)) {
@@ -625,3 +627,12 @@ check_condition_dots <- function(
625627
call = error_call
626628
)
627629
}
630+
631+
actual_condition <- function(cond) {
632+
paste0(
633+
"Actually got a <",
634+
class(cond)[[1]],
635+
"> with message:\n",
636+
indent_lines(cnd_message(cond))
637+
)
638+
}

0 commit comments

Comments
 (0)