Skip to content

Commit e40ce96

Browse files
committed
Merged origin/main into more-match-improvements
2 parents 986267a + c0c1fff commit e40ce96

File tree

90 files changed

+6100
-726
lines changed

Some content is hidden

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

90 files changed

+6100
-726
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, synchronize]
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

NAMESPACE

Lines changed: 3 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)

NEWS.md

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

3+
* 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)
4+
* `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)
5+
* `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.
6+
* `snapshot_accept(test)` now works when the test file name contains `.` (#1669).
7+
* `local_mock()` and `with_mock()` have been deprecated because they are no longer permitted in R 4.5.
8+
* `snapshot_review()` now passes `...` on to `shiny::runApp()` (#1928).
9+
* `expect_named()` now gives more informative errors (#2091).
10+
* `expect_*()` functions consistently and rigorously check their inputs (#1754).
311
* `test_that()` no longer warns about the absence of `{}` since it no longer seems to be necessary.
412
* `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).
513
* 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 +45,7 @@
3745
* Fixed an issue preventing compilation from succeeding due to deprecation / removal of `std::uncaught_exception()` (@kevinushey, #2047).
3846
* 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)
3947
* `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).
48+
* New expectation, `expect_shape()`, for testing the shape (i.e., the `nrow()`, `ncol()`, or `dim()`), all in one place (#1423, @michaelchirico).
4149

4250
# testthat 3.2.3
4351

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-equality.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ expect_waldo_equal_ <- function(
126126
exp,
127127
info = NULL,
128128
...,
129-
trace_env = caller_env()
129+
trace_env = caller_env(),
130+
error_prefix = NULL
130131
) {
131132
comp <- waldo_compare(
132133
act$val,
@@ -145,6 +146,7 @@ expect_waldo_equal_ <- function(
145146
"`expected`",
146147
paste0(comp, collapse = "\n\n")
147148
)
149+
msg <- paste0(error_prefix, msg)
148150
return(fail(msg, info = info, trace_env = trace_env))
149151
}
150152
pass(act$val)

R/expect-length.R

Lines changed: 0 additions & 27 deletions
This file was deleted.

R/expect-named.R

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,24 @@ expect_named <- function(
3636
check_bool(ignore.case)
3737

3838
act <- quasi_label(enquo(object), label)
39-
act$names <- names(act$val)
4039

4140
if (missing(expected)) {
42-
if (identical(act$names, NULL)) {
43-
msg <- sprintf("%s does not have names.", act$lab)
44-
return(fail(msg))
45-
}
46-
} else {
47-
exp_names <- normalise_names(expected, ignore.order, ignore.case)
48-
act$names <- normalise_names(act$names, ignore.order, ignore.case)
41+
return(expect_has_names_(act))
42+
}
43+
44+
exp <- quasi_label(enquo(expected), arg = "expected")
4945

50-
if (!identical(act$names, exp_names)) {
51-
msg <- sprintf(
52-
"Names of %s (%s) don't match %s",
53-
act$lab,
54-
paste0("'", act$names, "'", collapse = ", "),
55-
paste0("'", exp_names, "'", collapse = ", ")
56-
)
57-
return(fail(msg, info = info))
58-
}
46+
exp$val <- normalise_names(exp$val, ignore.order, ignore.case)
47+
act_names <- normalise_names(names(act$val), ignore.order, ignore.case)
48+
49+
if (ignore.order) {
50+
act <- labelled_value(act_names, act$lab)
51+
return(expect_setequal_(act, exp, error_prefix = "Names of "))
52+
} else {
53+
act <- labelled_value(act_names, act$lab)
54+
return(expect_waldo_equal_("equal", act, exp, error_prefix = "Names of "))
5955
}
56+
6057
pass(act$val)
6158
}
6259

@@ -74,3 +71,12 @@ normalise_names <- function(x, ignore.order = FALSE, ignore.case = FALSE) {
7471

7572
x
7673
}
74+
75+
expect_has_names_ <- function(act, trace_env = caller_env()) {
76+
act_names <- names(act$val)
77+
if (identical(act_names, NULL)) {
78+
msg <- sprintf("%s does not have names.", act$lab)
79+
return(fail(msg, trace_env = trace_env))
80+
}
81+
return(pass(act$val))
82+
}

R/expect-setequal.R

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,23 @@ expect_setequal <- function(object, expected) {
3434
testthat_warn("expect_setequal() ignores names")
3535
}
3636

37+
expect_setequal_(act, exp)
38+
}
39+
40+
expect_setequal_ <- function(
41+
act,
42+
exp,
43+
trace_env = caller_env(),
44+
error_prefix = NULL
45+
) {
3746
act_miss <- unique(act$val[!act$val %in% exp$val])
3847
exp_miss <- unique(exp$val[!exp$val %in% act$val])
3948

4049
if (length(exp_miss) || length(act_miss)) {
41-
return(fail(paste0(
50+
msg <- paste0(
51+
if (!is.null(error_prefix)) {
52+
error_prefix
53+
},
4254
act$lab,
4355
" (`actual`) and ",
4456
exp$lab,
@@ -49,7 +61,8 @@ expect_setequal <- function(object, expected) {
4961
if (length(exp_miss)) {
5062
paste0("* Only in `expected`: ", values(exp_miss), "\n")
5163
}
52-
)))
64+
)
65+
return(fail(msg, trace_env = trace_env))
5366
}
5467
pass(act$val)
5568
}

0 commit comments

Comments
 (0)