Skip to content

Commit 14dff09

Browse files
authored
Merge branch 'main' into snapshot-skip
2 parents ce7efbe + 0c13060 commit 14dff09

Some content is hidden

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

61 files changed

+1052
-300
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

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: 4 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)

NEWS.md

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

33
* `snapshot_review()` includes a reject button and only displays the file navigation and the skip button if there are multiple files to review (#2025).
4+
* New `snapshot_download_gh()` makes it easy to get snapshots off GitHub and into your local package (#1779).
5+
* 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)
6+
* `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)
47
* `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.
58
* `snapshot_accept(test)` now works when the test file name contains `.` (#1669).
69
* `local_mock()` and `with_mock()` have been deprecated because they are no longer permitted in R 4.5.
@@ -44,7 +47,7 @@
4447
* Fixed an issue preventing compilation from succeeding due to deprecation / removal of `std::uncaught_exception()` (@kevinushey, #2047).
4548
* 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)
4649
* `skip_on_os()` gains an option `"emscripten"` of the `os` argument to skip tests on Emscripten (@eitsupi, #2103).
47-
* New expectation, `expect_shape()`, for testing the shape (i.e., the `length()`, `nrow()`, `ncol()`, or `dim()`), all in one place (#1423, @michaelchirico).
50+
* New expectation, `expect_shape()`, for testing the shape (i.e., the `nrow()`, `ncol()`, or `dim()`), all in one place (#1423, @michaelchirico).
4851

4952
# testthat 3.2.3
5053

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

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

R/expect-match.R

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,29 +117,73 @@ expect_match_ <- function(
117117
return(pass(act$val))
118118
}
119119

120-
text <- encodeString(act$val)
121120
if (length(act$val) == 1) {
122-
values <- paste0(title, ': "', text, '"')
123121
which <- ""
124122
} else {
125-
bullet <- ifelse(
126-
condition,
127-
cli::col_green(cli::symbol$tick),
128-
cli::col_red(cli::symbol$cross)
129-
)
130-
values <- paste0(title, ":\n", paste0(bullet, " ", text, collapse = "\n"))
131123
which <- if (all) "Every element of " else "Some element of "
132124
}
133125
match <- if (negate) "matches" else "does not match"
134126

135127
msg <- sprintf(
136-
"%s%s %s %s %s.\n%s",
128+
"%s%s %s %s %s.\n%s:\n%s",
137129
which,
138130
act$lab,
139131
match,
140132
if (fixed) "string" else "regexp",
141133
encodeString(regexp, quote = '"'),
142-
values
134+
title,
135+
paste0(show_text(act$val, condition), collapse = "\n")
143136
)
144137
return(fail(msg, info = info, trace_env = trace_env))
145138
}
139+
140+
141+
# Adapted from print.ellmer_prompt
142+
show_text <- function(
143+
x,
144+
condition,
145+
...,
146+
max_items = 20,
147+
max_lines = max_items * 25
148+
) {
149+
n <- length(x)
150+
n_extra <- length(x) - max_items
151+
if (n_extra > 0) {
152+
x <- x[seq_len(max_items)]
153+
condition <- condition[seq_len(max_items)]
154+
}
155+
156+
if (length(x) == 0) {
157+
return(character())
158+
}
159+
160+
bar <- if (cli::is_utf8_output()) "\u2502" else "|"
161+
162+
id <- ifelse(
163+
condition,
164+
cli::col_green(cli::symbol$tick),
165+
cli::col_red(cli::symbol$cross)
166+
)
167+
168+
indent <- paste0(id, " ", bar, " ")
169+
exdent <- paste0(" ", cli::col_grey(bar), " ")
170+
171+
x[is.na(x)] <- cli::col_red("<NA>")
172+
x <- paste0(indent, x)
173+
x <- gsub("\n", paste0("\n", exdent), x)
174+
175+
lines <- strsplit(x, "\n")
176+
ids <- rep(seq_along(x), length(lines))
177+
lines <- unlist(lines)
178+
179+
if (length(lines) > max_lines) {
180+
lines <- lines[seq_len(max_lines)]
181+
lines <- c(lines, paste0(exdent, "..."))
182+
n_extra <- n - ids[max_lines - 1]
183+
}
184+
185+
if (n_extra > 0) {
186+
lines <- c(lines, paste0("... and ", n_extra, " more.\n"))
187+
}
188+
lines
189+
}

R/expect-shape.R

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,44 @@
1-
#' Do you expect an object with this shape?
1+
#' Do you expect an object with this length or shape?
22
#'
3-
#' This is a generalization of [expect_length()] to test the "shape" of
4-
#' more general objects like data.frames, matrices, and arrays.
3+
#' `expect_length()` inspects the [length()] of an object; `expect_shape()`
4+
#' inspects the "shape" (i.e. [nrow()], [ncol()], or [dim()]) of
5+
#' higher-dimensional objects like data.frames, matrices, and arrays.
56
#'
6-
#' @seealso [expect_length()] to specifically make assertions about the
7-
#' [length()] of a vector.
7+
#' @seealso [expect_vector()] to make assertions about the "size" of a vector.
88
#' @inheritParams expect_that
9-
#' @param ... Ignored.
10-
#' @param nrow,ncol Expected [nrow()]/[ncol()] of `object`.
11-
#' @param dim Expected [dim()] of `object`.
9+
#' @param n Expected length.
1210
#' @family expectations
1311
#' @export
1412
#' @examples
13+
#' expect_length(1, 1)
14+
#' expect_length(1:10, 10)
15+
#' show_failure(expect_length(1:10, 1))
16+
#'
1517
#' x <- matrix(1:9, nrow = 3)
1618
#' expect_shape(x, nrow = 3)
19+
#' show_failure(expect_shape(x, nrow = 4))
1720
#' expect_shape(x, ncol = 3)
21+
#' show_failure(expect_shape(x, ncol = 4))
1822
#' expect_shape(x, dim = c(3, 3))
23+
#' show_failure(expect_shape(x, dim = c(3, 4, 5)))
24+
expect_length <- function(object, n) {
25+
check_number_whole(n, min = 0)
26+
27+
act <- quasi_label(enquo(object))
28+
act$n <- length(act$val)
29+
30+
if (act$n != n) {
31+
msg <- sprintf("%s has length %i, not length %i.", act$lab, act$n, n)
32+
return(fail(msg))
33+
}
34+
pass(act$val)
35+
}
36+
37+
#' @param nrow,ncol Expected [nrow()]/[ncol()] of `object`.
38+
#' @param dim Expected [dim()] of `object`.
39+
#' @rdname expect_length
40+
#' @param ... Not used; used to force naming of other arguments.
41+
#' @export
1942
expect_shape = function(object, ..., nrow, ncol, dim) {
2043
check_dots_empty()
2144
check_exclusive(nrow, ncol, dim)

R/local.R

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,7 @@ local_test_directory <- function(path, package = NULL, .env = parent.frame()) {
190190
}
191191

192192
local_interactive_reporter <- function(.env = parent.frame()) {
193-
# Definitely not on CRAN
194-
withr::local_envvar(NOT_CRAN = "true", .local_envir = .env)
193+
local_assume_not_on_cran(.env)
195194
withr::local_options(testthat_interactive = TRUE, .local_envir = .env)
196195

197196
# Use edition from working directory

0 commit comments

Comments
 (0)