Skip to content

Commit 3e31ea7

Browse files
committed
Merge commit '5d0d48d9a01a101b1c67009dfdf5ac976589a177'
2 parents c4bdd9b + 5d0d48d commit 3e31ea7

File tree

113 files changed

+2918
-956
lines changed

Some content is hidden

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

113 files changed

+2918
-956
lines changed
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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
* `expect_snapshot_file()` now considers `.json` to be a text file (#1593).
44
* `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)
511
* `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)
612
* `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.
713
* `snapshot_accept(test)` now works when the test file name contains `.` (#1669).

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)