Release v1.46.0 - #1830
Conversation
Raw floor() on a Flesch-Kincaid grade between 0.01 and 0.99 collapsed to 0, causing valid simple-grade content to be treated as "not enough content" and flagged as failing. Introduces edac_normalize_fk_grade() which returns 0 only for a true zero grade and otherwise returns max(1, floor($fk_grade)). Applied to all four call sites across class-ajax.php (×2), class-summary-generator.php, and class-rest-api.php. Adds unit tests covering the boundary cases. Fixes #1497 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The fleschKincaidGradeLevel() library method returns false when content has no words or sentences. Remove the strict float type hint and cast to float internally so empty-content posts don't throw a TypeError. Add false and null test cases to cover this path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Release tooling will replace x.x.x with the actual version on release. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds test files for color_contrast_failure, text_justified, link_ambiguous_text, link_pdf, link_ms_office_file, and label rules (closes #1692). Also fixes a bug in image-input-has-alt.js where null?.trim() returned undefined instead of failing for textareas and other non-image-input elements, which caused the label rule to pass textareas with no label. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
axe.configure() cannot replace the built-in color-contrast check's evaluate binding at runtime, and the built-in check requires canvas pixel-sampling which JSDOM doesn't implement. Instead, the test spreads the real rule config (preserving id, tags, selector), replaces the canvas-dependent matches filter with a simple CSS visibility check, and registers a new 'color-contrast-cssonly' check that reads getComputedStyle directly. This gives 13 meaningful tests covering passing cases (high contrast, large text exceptions), failing cases (below 4.5:1 and 3:1 thresholds), and edge cases (hidden/empty elements). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The rule has no custom logic — it delegates entirely to axe-core built-ins (color-contrast-matches matcher + color-contrast check), both of which require canvas pixel-sampling unavailable in JSDOM. The previous approach replaced both built-ins with heavy mocks, effectively testing custom math that isn't in production code. These tests verify what is actually ours: the rule's id, tags, and built-in check delegation. Behavioral coverage requires a real browser. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds two cases flagged in code review: image input with no alt attribute and image input with whitespace-only alt. Both should fail the label rule via image_input_has_alt, which requires alt !== null && alt.trim() !== ''. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- textJustified: move heading test case to the failing block (was misplaced under the passing-cases comment) - colorContrastFailure: use toEqual(['color-contrast']) instead of toContain so the delegation contract is exact, not just partial - linkPdf, linkMsOfficeFile: add documented test cases for the known gap where a file extension followed by & in a query string is not matched by the CSS attribute selectors; explains why adding the selector variant risks false positives Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a second describe block that registers the rule with axe and runs it against a visible text element. In JSDOM, axe cannot sample canvas pixels so it marks evaluated elements as incomplete rather than violated — asserting incomplete.length > 0 and that the rule is not in inapplicable proves the rule's selector and matcher are functional, not just that the exported config object has the right shape. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cover blocks legitimately place aria-hidden="true" on background image and overlay elements (wp-block-cover__background, wp-block-cover__image-background). These are decorative and should not be flagged. Excludes any element whose class contains "wp-block-cover" at the selector level so the check never runs on them, rather than special-casing them in the check logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wp-block-spacer and wp-block-separator were excluded via early-return true in the aria_hidden_valid_usage check. Since these are unconditional by class name they belong at the selector level, which is faster (axe never instantiates the check for those elements) and makes the rule's scope self-documenting. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds three passing cases for cover block elements (background overlay, background image, container) that were incorrectly flagged before PRO-966. Also adds a regression describe block that reconfigures axe with the old bare [aria-hidden="true"] selector to confirm the violations would have been raised — proving the :not([class*="wp-block-cover"]) exclusion is what fixes them, not a silent change in axe or the check logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sses The broad [class*="wp-block-cover"] substring match excluded ANY element whose class contained that string — including the outer .wp-block-cover container and .wp-block-cover__inner-container, which hold real user content. If aria-hidden="true" were incorrectly placed on either, the checker would silently miss it. WordPress core only places aria-hidden="true" on two specific decorative child elements: .wp-block-cover__background (colour overlay) and .wp-block-cover__image-background (background image). Use those exact classes in the selector instead. Also adds a comment documenting each exclusion, and replaces the false- negative passing test for the outer container with a correct failing test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- colorContrastFailure: assert incomplete result by rule ID (not just length) so the check is robust if runOnly semantics ever change; also clean up the canvas prototype mock in afterAll alongside axe.reset() - label: add missing 'should fail for radio button with no label' case (checkbox had a failing test, radio did not); add afterAll axe.reset() for consistency with other test files - image-input-has-alt: replace the wrong JSDoc (copy-pasted from a different check) with an accurate description of what the function does Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[Backport] Release v1.45.0
github.event.release.tag_name was passed straight through to the WordPress.org deploy action as VERSION, so a GitHub release tagged v1.45.0 produced an SVN tag literally named v1.45.0. WP.org requires bare version numbers (matching the readme's Stable tag), so the mismatched tag never got picked up as the live release - had to be renamed manually on svn. Co-Authored-By: Claude <noreply@anthropic.com>
…v-prefix-svn-deploy-version fix: strip leading v from release tag before using as SVN version
… found Closes #1807 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d DismissPanel tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ubpath matches Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ss-icon-when-no-issues fix: show success icon when no issues found in Accessibility Analysis panel
The Gutenberg Audio block's Ogg Vorbis sample file was being detected as video because .ogg is also a valid Ogg Theora video extension. Only treat a .ogg match as video when it isn't attached to an <audio> element or one of its <source> children. Fixes #1816, PRO-1168. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Only compute node.parentNode's tag when the node is a <source>, and lowercase src/data once instead of on every iteration of the extensions loop, per Gemini code review feedback on PR #1817. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…slations_july-08-2026-18-43-12797590 PTC accessibility-checker (1985): Automatic Translations July 08, 2026 18:43:12797590
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request bumps the plugin version to 1.46.0 and introduces Flesch-Kincaid grade level normalization to prevent simple content from being incorrectly flagged. It also updates several accessibility rules (such as excluding decorative cover blocks from aria-hidden checks and preventing Ogg audio from being flagged as video), adds comprehensive Jest and PHPUnit tests, and automates @SInCE tag updates during release preparation. The review feedback highlights a compatibility issue with mapfile -d in older Bash versions on macOS, a potential TypeError in image-input-has-alt.js if alt is undefined, and a test isolation issue in colorContrastFailure.test.js where the mocked getContext method is deleted rather than restored.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
|
||
| # Stage only the tracked PHP files modified by the tool. | ||
| # Use NUL-delimited output + mapfile + xargs -0 so paths with spaces are safe. | ||
| mapfile -d '' SINCE_CHANGES < <(git diff --name-only -z -- '*.php') |
There was a problem hiding this comment.
The mapfile -d option was introduced in Bash 4.4. However, macOS still ships with Bash 3.2 by default, where this option is not supported and will cause the script to fail with an invalid option error.
To ensure compatibility across development environments (especially macOS), consider using a while read loop with read -d to safely parse the NUL-delimited output.
| mapfile -d '' SINCE_CHANGES < <(git diff --name-only -z -- '*.php') | |
| SINCE_CHANGES=() | |
| while IFS= read -r -d '' line; do | |
| SINCE_CHANGES+=("$line") | |
| done < <(git diff --name-only -z -- '*.php') |
There was a problem hiding this comment.
Good catch — applied in ab7e2ff with a while read -r -d '' loop. Worth noting it's slightly worse than stated: macOS system bash (3.2) lacks mapfile entirely, not just the -d option, so the script would have died at this step on any Mac using system bash. Verified the replacement handles paths with spaces and empty input identically.
| const alt = node.getAttribute( 'alt' ); | ||
| return alt !== null && alt.trim() !== ''; |
There was a problem hiding this comment.
If alt is undefined (which can occur in some non-standard DOM environments or custom test mocks), alt !== null will evaluate to true, but calling alt.trim() will throw a TypeError.
To make this check more robust and defensive, verify that alt is explicitly of type string before calling .trim() on it.
| const alt = node.getAttribute( 'alt' ); | |
| return alt !== null && alt.trim() !== ''; | |
| const alt = node.getAttribute( 'alt' ); | |
| return typeof alt === 'string' && alt.trim() !== ''; |
There was a problem hiding this comment.
Declining — per the DOM spec, Element.getAttribute() returns a string or null, never undefined, and that holds in every environment this check runs in: real browsers via the axe-core page scanner, and jsdom in the Jest suite. The premise ("non-standard DOM environments or custom test mocks") describes environments this code doesn't execute in, so alt !== null && alt.trim() !== '' is safe as written and the typeof guard would be defensive noise.
| beforeAll( async () => { | ||
| // The color-contrast-matches built-in matcher calls _isIconLigature, which | ||
| // needs HTMLCanvasElement.getContext. Without it, axe throws and excludes | ||
| // every element before the check runs. Provide a minimal mock so the matcher | ||
| // completes (equal measureText widths → not an icon ligature → element passes | ||
| // through to the contrast check). | ||
| HTMLCanvasElement.prototype.getContext = function() { | ||
| return { | ||
| font: '', | ||
| measureText: ( text ) => ( { width: text.length * 8 } ), | ||
| fillText: () => {}, | ||
| clearRect: () => {}, | ||
| fillRect: () => {}, | ||
| drawImage: () => {}, | ||
| getImageData: () => ( { data: new Uint8ClampedArray( 4 ) } ), | ||
| }; | ||
| }; | ||
|
|
||
| axe.configure( { rules: [ colorContrastRule ] } ); | ||
| } ); | ||
|
|
||
| afterAll( () => { | ||
| delete HTMLCanvasElement.prototype.getContext; | ||
| axe.reset(); | ||
| } ); |
There was a problem hiding this comment.
Using delete HTMLCanvasElement.prototype.getContext completely removes the getContext method from the prototype. This can pollute the global environment and potentially break other tests running after this suite if they rely on the default JSDOM canvas implementation (which returns null or uses the canvas package).
A safer approach is to store the original getContext method in beforeAll and restore it in afterAll to ensure proper test isolation.
let originalGetContext;
beforeAll( async () => {
originalGetContext = HTMLCanvasElement.prototype.getContext;
// The color-contrast-matches built-in matcher calls _isIconLigature, which
// needs HTMLCanvasElement.getContext. Without it, axe throws and excludes
// every element before the check runs. Provide a minimal mock so the matcher
// completes (equal measureText widths -> not an icon ligature -> element passes
// through to the contrast check).
HTMLCanvasElement.prototype.getContext = function() {
return {
font: '',
measureText: ( text ) => ( { width: text.length * 8 } ),
fillText: () => {},
clearRect: () => {},
fillRect: () => {},
drawImage: () => {},
getImageData: () => ( { data: new Uint8ClampedArray( 4 ) } ),
};
};
axe.configure( { rules: [ colorContrastRule ] } );
} );
afterAll( () => {
HTMLCanvasElement.prototype.getContext = originalGetContext;
axe.reset();
} );There was a problem hiding this comment.
Declining — Jest gives every test file its own fresh jsdom environment, so prototype mutations cannot leak across suites, and within this file the delete runs in afterAll with nothing executing after it. There's no pollution path here. Save-and-restore is marginally tidier style and worth adopting if these suites ever share an environment, but there's no defect as things stand.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mapfile -d requires Bash 4.4 and macOS system bash is 3.2, which lacks mapfile entirely, so release prep would die at the @SInCE staging step. The while read -d '' loop is functionally identical and 3.2-safe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
✅ Accessibility Checker build (primary only)
|
Changelog items from merges on develop since main (from
scripts/prep_release.sh):Steve Jones (6):
William Patton (11):
Verified: the new
tools/update-since-tags.phpstep rewrote all@since x.x.xplaceholders in files changed since v1.45.0 to1.46.0(commit 0f660e2); no placeholders remain in changed PHP files.🤖 Generated with Claude Code