Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ $repo->find(123))->returns($book)`. There are no method-name strings and no
service methods on the double itself.

Public API lives in `Rasuvaeff\Understudy`: the `Understudy` facade, the free
functions `when()`/`verify()` in `src/functions.php`, `Arg`, `Invocation`,
`Outcome`, `WhenBuilder`, and the exceptions under `Exception\`. Everything
functions `when()`/`expect()`/`expectSequence()`/`verify()` in
`src/functions.php`, `Arg`, `Invocation`, `Outcome`, `WhenBuilder`, and the
exceptions under `Exception\`. Everything
under `Codegen\`, `Runtime\`, `Expectation\`, `Defaults\` and `Matcher\` is
`@internal`.

Expand Down Expand Up @@ -438,7 +439,8 @@ seconds instead of the full run's minute — and the full run stays the gate.

`docs/` holds the family site — one VitePress site for all five packages,
deployed to <https://rasuvaeff.github.io/understudy/> from `master`. The plan
it was built from is `_plans/UNDERSTUDY-DOCS-SITE-PLAN.md` in the monorepo.
it was built from was retired from the monorepo once the site shipped; what it
decided lives in this section and in `docs/scripts/check-integrity.mjs`.

| Rule | Why |
|---|---|
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## Unreleased

- **Documentation review fixes.** llms.txt no longer claims `bypassFinals()`
and the runner adapters are «being built next» (both shipped long ago), and
now lists all four free functions and the `: static` loose-default rule.
The Prophecy migration table no longer teaches a non-existent
`expect(…)->never()` — the supported form is `expect(…)->times(0)`.
`idle()` comments across the READMEs, llms.txt and the guide now say what
the operation does: it covers every context of the test, not the current
one. The Performance table's `5.38³` footnote marker was a damaged
character. `examples/README.md` now documents `case-studies/` and the gate
that runs it (`make docs-cookbook`). AGENTS.md's dead link to the retired
site plan replaced with where the decisions live.

## 0.7.1 — 2026-09-04

- **`bypassFinals()` missed a target whose declaration is written in another
Expand Down
2 changes: 1 addition & 1 deletion MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ PHPUnit 10 dropped Prophecy from the box, so this is a common second hop.
| `Argument::any()` / `type()` / `that()` | `Arg::any()` / `Arg::instanceOf()` / `Arg::satisfies()` |
| `->shouldBeCalled()` | `expect(…)` |
| `->shouldBeCalledTimes(n)` | `expect(…)->times(n)` |
| `->shouldNotBeCalled()` | `expect(…)->never()` |
| `->shouldNotBeCalled()` | `expect(…)->times(0)` |
| `->shouldHaveBeenCalled()` | [`verify(…)`](https://rasuvaeff.github.io/understudy/guide/expectations/verify) |
| `$prophet->checkPredictions()` | `Understudy::verifyAll()`, or the adapter |

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ tables and the environment in [perf/README.md](perf/README.md).
| stub: build, stub, one call, tear down | 10.6µs | +17% | +76% | **−17%**¹ |
| mock: build, expect, call, verify | 12.8µs | +4% | +128% | **−27%**² |
| marginal cost of one call to a stub | 0.86µs | 1.61µs | 1.51µs | **0.69µs**¹ |
| added to process start (cold) | **1.00×** | 1.50× | 4.96× | 5.38׳ |
| added to process start (cold) | **1.00×** | 1.50× | 4.96× | 5.38³ |
| retained per live double | **467–482 B** | 513 B | ~8.5 KB | ~1.25 KB |

¹ `createStub()` ² `createMock()` ³ a ratio rather than milliseconds: cold start
Expand Down Expand Up @@ -500,7 +500,7 @@ stopped at.
Understudy::checkpoint(); // verify, then forget what is settled
$result = Understudy::scope(fn () => ...); // nested context, verified on success
echo Understudy::transcript($repository); // every call and its outcome
Understudy::idle(); // true when the context holds no doubles
Understudy::idle(); // true when the test holds no doubles, in any context
```

`transcript()` retains every invocation until `reset()` or `checkpoint()`.
Expand Down Expand Up @@ -810,7 +810,7 @@ prose.

```php
Understudy::reset();
Understudy::idle(); // true when the current context holds no doubles
Understudy::idle(); // true when the test holds no doubles, in any context
```

The [understudy-testo](https://github.com/rasuvaeff/understudy-testo) and
Expand Down Expand Up @@ -870,7 +870,7 @@ so `bin/package-audit` runs them as a gate rather than linting them.
## Development

```bash
make build # validate, normalize, require-checker, cs, psalm, test
make build # validate, normalize, require-checker, cs, psalm, unit, integration, examples
make cs-fix
make psalm
make test
Expand Down
8 changes: 4 additions & 4 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ when(fn () => $repository->find(123))->returns($book);
| stub: создать, застабить, вызвать, снести | 10.6µs | +17% | +76% | **−17%**¹ |
| mock: создать, expect, вызвать, verify | 12.8µs | +4% | +128% | **−27%**² |
| предельная цена одного вызова к стабу | 0.86µs | 1.61µs | 1.51µs | **0.69µs**¹ |
| добавляет к старту процесса (холодный) | **1.00×** | 1.50× | 4.96× | 5.38׳ |
| добавляет к старту процесса (холодный) | **1.00×** | 1.50× | 4.96× | 5.38³ |
| удерживает на каждый живой дубль | **467–482 B** | 513 B | ~8.5 KB | ~1.25 KB |

¹ `createStub()` ² `createMock()` ³ отношение, а не миллисекунды: холодный
Expand Down Expand Up @@ -501,7 +501,7 @@ The protocol is:
Understudy::checkpoint(); // проверить и забыть завершённое
$result = Understudy::scope(fn () => ...); // вложенный контекст, проверяемый при успехе
echo Understudy::transcript($repository); // все вызовы и их исходы
Understudy::idle(); // true, если в контексте нет дублей
Understudy::idle(); // true, когда тест не держит дублей ни в одном контексте
```

`transcript()` хранит каждый вызов до `reset()` или `checkpoint()`. Не
Expand Down Expand Up @@ -808,7 +808,7 @@ Configure it first: when(fn () => $double->tag(...))->returns(...)

```php
Understudy::reset();
Understudy::idle(); // true, если в текущем контексте нет ни одного дубля
Understudy::idle(); // true, когда тест не держит дублей ни в одном контексте
```

Адаптеры [understudy-testo](https://github.com/rasuvaeff/understudy-testo) и
Expand Down Expand Up @@ -869,7 +869,7 @@ Understudy генерирует по классу на набор контрак
## Разработка

```bash
make build # validate, normalize, require-checker, cs, psalm, test
make build # validate, normalize, require-checker, cs, psalm, unit, integration, examples
make cs-fix
make psalm
make test
Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/lifecycle/forget.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ One-way, like every other form of forgetting here.

```php
Understudy::reset();
Understudy::idle(); // true when the current context holds no doubles
Understudy::idle(); // true when the test holds no doubles, in any context
```

The [Testo](/adapters/testo) and [PHPUnit](/adapters/phpunit) adapters call
Expand Down
4 changes: 2 additions & 2 deletions docs/src/guide/lifecycle/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: "checkpoint(), scope(), transcript() and the call log — what owns
Understudy::checkpoint(); // verify, then forget what is settled
$result = Understudy::scope(fn () => ...); // nested context, verified on success
echo Understudy::transcript($repository); // every call and its outcome
Understudy::idle(); // true when the context holds no doubles
Understudy::idle(); // true when the test holds no doubles, in any context
```

## The context
Expand Down Expand Up @@ -77,7 +77,7 @@ problem, not a size one.

```php
Understudy::reset();
Understudy::idle(); // true when the current context holds no doubles
Understudy::idle(); // true when the test holds no doubles, in any context
```

The [Testo](/adapters/testo) and [PHPUnit](/adapters/phpunit) adapters verify
Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/migrating-from-phpunit.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ PHPUnit 10 dropped Prophecy from the box, so this is a common second hop.
| `Argument::any()` / `type()` / `that()` | `Arg::any()` / `Arg::instanceOf()` / `Arg::satisfies()` |
| `->shouldBeCalled()` | `expect(…)` |
| `->shouldBeCalledTimes(n)` | `expect(…)->times(n)` |
| `->shouldNotBeCalled()` | `expect(…)->never()` |
| `->shouldNotBeCalled()` | `expect(…)->times(0)` |
| `->shouldHaveBeenCalled()` | [`verify(…)`](/guide/expectations/verify) |
| `$prophet->checkPredictions()` | `Understudy::verifyAll()`, or the adapter |

Expand Down
6 changes: 6 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ demonstrate changes.
`_check.php` is the shared assertion helper the scripts include; the leading
underscore is what marks it an include rather than a script of its own.

`case-studies/` holds the cookbook scenarios: each one reproduces a real
failure message quoted on a [cookbook](docs/src/cookbook) page of the
documentation site. They are gated by `make docs-cookbook`, which diffs their
output against the pages — not by `composer build` — and their `_bootstrap.php`
is the include, same convention as `_check.php`.

There is deliberately no `bypassFinals()` example. It depends on load order and
on the source arriving through `file://`, so a script cannot assert its own
outcome everywhere; those claims live in the acceptance scenarios
Expand Down
12 changes: 7 additions & 5 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ No runtime deps; `ext-mbstring` is not needed.
- The closure must contain **exactly one** direct call on a double. Anything
else raises `InvalidCallSpecification`.
- A double exposes nothing beyond its contract. All operations are static
methods on `Understudy`, or the free functions `when()` / `verify()`.
methods on `Understudy`, or the free functions `when()` / `expect()` /
`expectSequence()` / `verify()`.
- Interfaces and classes. A class may be the first target only (PHP has single
inheritance); interfaces follow it. The target's constructor and destructor
never run, private and static methods are left alone, protected ones are
Expand Down Expand Up @@ -347,7 +348,7 @@ where the stack trace points at `verifyAll()`. Rules, all of them load-bearing:
Understudy::checkpoint(); // verify, then forget what is settled
$result = Understudy::scope(fn () => ...); // nested context, verified on success
Understudy::transcript($repo); // every call and outcome, for reading
Understudy::idle(); // true when the current context holds no doubles
Understudy::idle(); // true when the test holds no doubles, in any context
```

`scope()` returns whatever the callback returns and drops its context either
Expand Down Expand Up @@ -408,7 +409,9 @@ Understudy::reset(); // drop the current context
Loose (default) answers with a type-safe default: `null` for `void`/`mixed`/
nullable, `false`, `0`, `0.0`, `''`, `[]`, `new stdClass()`, an empty
`Generator`, an `EmptyIterator`, a no-op callable. A type with no safe default
raises `NoDefaultValue`; a `never` method raises `NeverMethodCalled`.
raises `NoDefaultValue`; a `never` method raises `NeverMethodCalled`. A method
declared `: static` answers with the double itself; `: self` names the contract
and answers with a nested double.

### Exceptions

Expand Down Expand Up @@ -465,5 +468,4 @@ change. New kinds and newly-populated fields are additive.
## Not supported

Static method mocking, patching at the point of use, deep stubs, duck doubles,
sharing state across processes. `bypassFinals()` and the runner adapters are
being built next.
sharing state across processes.
Loading