Skip to content

feat(assert): add comparison modifiers and new checks to the string assertions - #367

Merged
roxblnfk merged 7 commits into
1.xfrom
feat/string-ignoring-modifiers
Sep 26, 2026
Merged

roxblnfk merged 7 commits into
1.xfrom
feat/string-ignoring-modifiers

Conversation

@roxblnfk

@roxblnfk roxblnfk commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

🔍 What was changed

  • Assert::string() gains ignoringCase(), ignoringLineEndings(), ignoringWhitespace(bool $lineBreaks = false), ignoringBlankLines() and ignoringAnsi(). Each returns a new chain; the original stays strict. Subject and check arguments are normalized by the same rules in a fixed order (ANSI, line endings, whitespace, blank lines, case). Regex checks see every mode except case; the pattern itself is never changed.
  • New string checks: same() / notSame() (strict === of the normalized strings, never numeric juggling; a same() failure diffs the normalized strings) and notStartsWith() / notEndsWith().
  • Substring, prefix and suffix arguments are declared non-empty-string; one that is not empty but normalizes to an empty string throws InvalidArgumentException. same('') stays a valid check.
  • The unreleased matchesPattern() / notMatchesPattern() are renamed to matchesRegex() / notMatchesRegex().
  • Expect::exception() gains withMessageMatchingRegex(); withMessagePattern() stays as a deprecated alias.
  • Rector, both directions: the IgnoringCase / IgnoringLineEndings substring and equality assertions, assertStringEqualsStringIgnoringWhitespace and its negation, assertEqualsIgnoringCase / assertNotEqualsIgnoringCase for two strings, assertStringStartsNotWith / assertStringEndsNotWith, plain same() / notSame() to assertSame / assertNotSame. Combinations with no PHPUnit form stay unconverted and are listed in TODO.md.
  • New Rector set TestoRectorSetList::TESTO_SHIFT for deprecated Testo API, first entry withMessagePattern() → withMessageMatchingRegex(). AGENTS.md now asks every deprecation to add its migration there.
  • The PHPUnit mirror no longer skips tests that use withMessageContaining(), which the Testo → PHPUnit conversion maps.

Review notes

  • MergeAssertChainRector merges string chains only when they open with the same modifier set, arguments compared as written. Without this guard, converting assertStringContainsStringIgnoringCase next to a plain assertStringContainsString would make the strict check case-insensitive.
  • testo/assert and the root package now require symfony/polyfill-mbstring.
  • Support::stringify() now escapes control characters other than tab and line breaks (\e, \x07), for every assertion. Raw ANSI in a failure message used to restyle the terminal. Support::dump() (used for diffs) is unchanged.
  • The testo-shift set configures Rector's RenameMethodRector, so it has no rule class for #[TestRectorFixtures]. RectorRunner can now import set files, and tests/Unit/TestoShiftSetTest.php runs the fixtures kept in config/testo-shift/.

Checklist

  • How was this tested:
    • Assert plugin suites (Self, Unit, Feature); the one failure is the Revolt test, which needs the tools/revolt install that is missing locally
    • Rector fixtures of the touched rules and the testo-shift set
    • psalm, php-cs-fixer
    • Full suite (not run)

@roxblnfk
roxblnfk requested a review from a team as a code owner September 26, 2026 10:26
@codecov

codecov Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.49749% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ridge/rector/src/Testing/Internal/RectorRunner.php 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@roxblnfk
roxblnfk force-pushed the feat/string-ignoring-modifiers branch 3 times, most recently from 5574412 to c573a7b Compare September 26, 2026 11:50
@roxblnfk roxblnfk changed the title feat(assert): add comparison modifiers to the string assertions feat(assert): add comparison modifiers and new checks to the string assertions Sep 26, 2026
`Assert::string()` gains `ignoringCase()`, `ignoringLineEndings()`,
`ignoringWhitespace()`, `ignoringBlankLines()` and `ignoringAnsi()`. Each
returns a new chain whose checks compare against a subject normalized once
from the original value, and normalizes every check argument the same way;
the original chain stays strict. An argument that normalizes to an empty
string is rejected, since it would match anything. Patterns see every mode
but the case one.

Failure messages now spell out control characters of asserted strings, so
escape codes no longer restyle the terminal. Case-insensitive comparison
uses `mb_strtolower()`, so the assert plugin now requires
symfony/polyfill-mbstring.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
`Assert::string()` gains `same()` and `notSame()`, a strict `===` of the
normalized string against the normalized expected one, so the comparison
modifiers apply to whole strings too. Numeric strings are never compared
as numbers. A `same()` failure carries the normalized strings for the diff
and quotes the original ones in the message.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
…ertions

The negated prefix and suffix checks follow the comparison modifiers like
the other substring checks, including the rejection of an argument that
normalizes to an empty string.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
…line endings or whitespace

PHPUnit's `assertStringContainsStringIgnoringCase`, its negation,
`assertStringContainsStringIgnoringLineEndings`,
`assertStringEqualsStringIgnoringLineEndings`, the whitespace-insensitive
equality pair and, for two strings, `assertEqualsIgnoringCase` and its
negation become `Assert::string()` chains with the matching modifier and
`contains()`, `notContains()`, `same()` or `notSame()`, and back. A plain
`same()`/`notSame()` becomes `assertSame`/`assertNotSame`, and
`assertStringStartsNotWith`/`assertStringEndsNotWith` convert to and from
`notStartsWith()`/`notEndsWith()`. The combinations PHPUnit has no
assertion for leave a chain untouched. Adjacent chains merge only when they
open with the same modifiers, so no check changes its mode.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
…recate `withMessagePattern()`

The regex string checks are renamed to `matchesRegex()` and
`notMatchesRegex()`. `withMessagePattern()` is deprecated in favour of
`withMessageMatchingRegex()`, which behaves the same.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
`TestoRectorSetList::TESTO_SHIFT` moves code off deprecated Testo API onto
its replacement; every future deprecation adds its migration there. The
first entry renames `withMessagePattern()` to `withMessageMatchingRegex()`
on the exception expectation through Rector's `RenameMethodRector`. The
fixture runner can now import set files, which runs the set's fixtures.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
The PHPUnit mirror skipped every test whose exception expectation used
`withMessageContaining()`, although the Testo to PHPUnit conversion maps it
to `expectExceptionMessage()`. Such tests now run under PHPUnit too.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
@roxblnfk
roxblnfk force-pushed the feat/string-ignoring-modifiers branch from c573a7b to 1d3582c Compare September 26, 2026 12:01
@roxblnfk
roxblnfk merged commit dedd2aa into 1.x Sep 26, 2026
29 of 31 checks passed
@roxblnfk
roxblnfk deleted the feat/string-ignoring-modifiers branch September 26, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant