Repository navigation
test: characterization suite for every feature and SVG source - #44
Merged
Merged
Conversation
The plugin had no tests. This adds 176 of them, describing what the plugin does today so that a later refactor of how icons load and cache can be shown not to change it. Covered: - every SVG source — folder, sprite, single file — including icon_map labels, sprite version cache-busting, the public URL a sprite icon resolves to, empty and unreadable inputs, and the blockparty_icons_svg_parse_tags filter - the collection container: lookup by name, the dotted-name key, replacement, count, search across both name and label - the registration API a theme calls, duplicate rejection, unknown types, and the editor's REST preload paths - front-end markup: raw and sprite icons, size, border radius, link and aria-label, all accepted and rejected colour formats, and URL/label escaping - both REST controllers: payload shape, pagination headers, search, single item, 404s, and the permission checks at every role - the object-cache wrapper, including that an empty result is a hit and not a miss - the KSES allowances that keep saved block markup valid, and that <script> inside an SVG is still stripped They are characterization tests: they assert observable behaviour — how many icons, with what names, labels, types and contents, and what markup reaches a page — and say nothing about when or how often files are read. A change that alters the mechanism leaves them green; a change that alters what a caller sees does not. Infrastructure: integration tests against a real WordPress rather than mocks, since the plugin leans on WP_Query, the object cache, WP_HTML_Tag_Processor, WP_REST_Controller and a set of filters. The bootstrap runs both through wp-env (npm run test:php) and against the Composer copy of WordPress (composer test), which is the path CI takes on PHP 8.1 through 8.4. Two findings recorded along the way, both in tests/README.md: Collection's Iterator never yields anything because it walks integer positions over name-keyed items, and the development-mode cache bypass cannot be exercised from a plugin suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All four PHPUnit jobs failed on their first run: 23 failures, every one a rendering test receiving an empty string, plus the assertion that the block type is registered. The cause was not the tests. The plugin calls register_block_type() on its build/ directory, build/ is git-ignored, and the workflow never compiled it — so the block never registered and render_block() had nothing to return. Confirmed locally by moving build/ aside: the same 23 failures over the same 408 assertions as CI. The workflow now builds the assets once in its own job and hands them to the matrix through an artifact, rather than running npm four times over, and checks build/block.json exists before handing over to PHPUnit. The bootstrap gained the same check with a pointed message, because a fresh clone lands in exactly this state and two dozen unexplained failures are a poor welcome. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6bbf18d. Configure here.
The workflow was standing up its own MariaDB service and generating a wp-tests-config.php, duplicating what wp-env already provides — and it is where the first CI failure came from. wp-env supplies WordPress, the WordPress PHPUnit library and the database, so the workflow now runs `npm run test:php`, the same command as locally. A green run on a laptop and a green run on the pull request now mean the same thing. The PHP matrix is kept: wp-env takes the version from WP_ENV_PHP_VERSION and pulls the matching upstream wordpress:php8.x image. Documented in tests/README.md, since it is also how you reproduce a single matrix job locally. The hand-rolled path stays supported for anyone without Docker — the bootstrap still falls back to the Composer copy of WordPress and tests/wp-tests-config-sample.php is still there — it just is not what CI uses any more. On failure the job prints the wp-env test-environment logs, which is the first thing anyone would ask for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 of 176 tests failed in CI on every PHP version, all with the same message: the fixture directory under wp-content could not be created. The runner script was doing `docker exec -u 33`. wp-env builds its containers around the *host* user — wp-content is owned by that UID and Apache runs as it — so 33 is only right by accident. On macOS it works because Docker Desktop's file sharing ignores ownership; on a Linux runner, where the host UID is 1001 and ownership is real, www-data cannot write there. The same mismatch was behind the PHPUnit result-cache permission warning. Resolving the UID at run time matches what wp-env actually configures, in both places. Verified locally on PHP 8.1, 8.2, 8.3 and 8.4 through wp-env: 176 tests, 427 assertions, green on each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review flagged the runner as never setting WP_TESTS_DIR, and concluded the suite must be falling back to the Composer copy of WordPress and a root wp-tests-config.php. It is not: wp-env sets the variable on its containers and docker exec inherits it, so the bootstrap takes its managed-environment branch and uses wp-env's WordPress, test library and database. The reading was wrong but the confusion was fair — the script leaned on a container-level variable it never mentioned. Both ends of that coupling now say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
The plugin had no tests. This adds them before the performance work in #42 lands, so that PR can be rebased on top and shown not to change behaviour — rather than asserted not to.
Written against
developas it stands today: 176 tests, 427 assertions, all green on the unmodified plugin.These are characterization tests
They assert observable behaviour — how many icons come back, with what names, labels, types and contents, and what markup reaches a page — and say nothing about when or how often files are read.
That is the whole design point. A change that alters only the mechanism (lazy loading, different cache keys, a new source type) leaves the suite green. A change that alters what a caller can see does not. That is what makes it a regression net for #42 rather than a cast of the current implementation.
Coverage
Icon/CollectionItemsFactoryTest.phpIcon/CollectionTest.phpIcon/CollectionItemTest.phpBlockRendererTest.phpRegistrationTest.phpRest/CollectionsControllerTest.phpRest/IconsControllerTest.phpHelpers/CacheTest.phpHelpers/KsesTest.phpWorth calling out specifically:
icon_maplabels, sprite version cache-busting, the public URL a sprite icon resolves to, empty files, unreadable paths, non-SVG files being ignored, and theblockparty_icons_svg_parse_tagsfilter.var:preset|color|x,var(--x),var(--x, #fff)) and the ones it must reject (named colours,rgb(),javascript:,expression(), a quote-break injection attempt).<script>inside an SVG is still stripped by KSES despite the plugin's allowances.Infrastructure
Integration tests against a real WordPress, not mocks: the plugin leans on
WP_Query, the object cache,WP_HTML_Tag_Processor,WP_REST_Controllerand a set of filters, and mocking that surface would test the mocks.Two ways to run, both verified:
CI runs the suite on PHP 8.1, 8.2, 8.3 and 8.4 (
.github/workflows/test-php.yml).Findings
Three things surfaced while writing this. None is caused by these tests; all are recorded in
tests/README.md.Collection's Iterator never yields anything. It walks an integer$positionwhileadd()keys items by name, sovalid()asks for$items[0], which never exists —foreachover a populated collection yields nothing. Nothing in the plugin iterates a collection (every caller usesall()), so it is latent.test_known_defect_iteration_yields_nothing_even_when_populatedrecords it and will fail when someone fixes it; update that test then rather than deleting it.BlockRenderer::render()cannot be called outside a block render pass on PHP 8.4. It callsget_block_wrapper_attributes(), which readsWP_Block_Supports::$block_to_render; that is null outside a render pass and PHP 8.4 warns. The tests go throughrender_block()instead, which is both closer to reality and warning-free. Not a plugin bug — real rendering always has that context — but worth knowing if anyone calls the renderer directly.The development-mode cache bypass is not coverable from a plugin suite. WordPress resolves
wp_is_development_mode()from a constant behind a static cache and only lets its own core suite vary it. The suite therefore runs with caching active, which is the production path anyway.For the reviewer
composer.jsongainedphpunit/phpunit ^9.6,yoast/phpunit-polyfillsandwp-phpunit/wp-phpunitas dev dependencies. Resolving them needed-W, which moved somesebastian/*versions; Psalm still reports the same 221 errors as before and phpcs is clean, so the toolchain is unaffected.wp-tests-config.phpis git-ignored (it holds credentials and machine paths); a sample lives attests/wp-tests-config-sample.php.tests/is excluded from releases already, via the existing/testsentry in.distignore../tests/fromphpcs.xml.distfor its perf harness, which would also stop linting these test files. Narrow it to./tests/perf/on rebase — the PHPUnit suite is phpcs-clean and should stay that way.Next
Once this is in, #42 rebases onto it. Any behaviour the optimisation changes will show up as a failing test.
🤖 Generated with Claude Code
Note
Low Risk
Adds tests and CI only; production plugin code is unchanged, so runtime risk is minimal aside from new dev dependencies and workflow secrets for private Composer repos.
Overview
Introduces a PHPUnit integration test suite (~176 tests) that runs against a real WordPress install via
wp-envor Composer +wp-tests-config.php, documented intests/README.md.The tests are characterization-style: they lock in observable behaviour for icon loading (folder, sprite, single file), collections, registration APIs, block front-end rendering (including colour validation and escaping), both REST controllers, object-cache helpers, and KSES allowances. Shared fixtures and isolation live in
tests/phpunit/TestCase.php; bootstrap enforces compiledbuild/block.jsonbefore rendering tests run.Tooling and CI: dev dependencies add PHPUnit 9.6,
wp-phpunit, and polyfills;composer testandnpm run test:php(viatests/bin/phpunit.shin the wp-env CLI container) run the suite. A new.github/workflows/test-php.ymlmatrix runs on PHP 8.1–8.4 (build assets,composer install,wp-env start, then PHPUnit). Local PHPUnit config paths are git-ignored;wp-tests-config.phpis excluded from dist builds.Reviewed by Cursor Bugbot for commit fc07015. Bugbot is set up for automated code reviews on this repo. Configure here.