Skip to content

Release v1.46.0 - #1830

Merged
pattonwebz merged 94 commits into
mainfrom
release/1.46.0
Jul 8, 2026
Merged

Release v1.46.0#1830
pattonwebz merged 94 commits into
mainfrom
release/1.46.0

Conversation

@pattonwebz

@pattonwebz pattonwebz commented Jul 8, 2026

Copy link
Copy Markdown
Member

Changelog items from merges on develop since main (from scripts/prep_release.sh):

Steve Jones (6):

  • fix: show success icon when no issues found in Accessibility Analysis panel
  • Fix inaccurate accessible name on settings navigation landmark
  • fix: convey summary status in Reading Level card accessible name

William Patton (11):

  • [Backport] Release v1.45.0
  • fix: strip leading v from release tag before using as SVN version
  • PRO-1168: fix: don't flag .ogg audio as video content in video_present rule
  • Exclude core cover blocks from aria-hidden rule and move spacer/separator to selector
  • Fix: FK reading grade of 0 < x < 1 collapses to 0 (normalize to 1)
  • Add @SInCE update tool and release prep integration
  • Tests: add Jest coverage for 6 scanner rules without tests
  • chore: update plugin URI to equalizedigital.com
  • Update POT translation file
  • chore: regenerate hooks docs
  • PTC accessibility-checker (1985): Automatic Translations July 08, 2026 18:43:12797590

Verified: the new tools/update-since-tags.php step rewrote all @since x.x.x placeholders in files changed since v1.45.0 to 1.46.0 (commit 0f660e2); no placeholders remain in changed PHP files.

🤖 Generated with Claude Code

SteveJonesDev and others added 30 commits May 28, 2026 19:59
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>
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>
PTC and others added 19 commits July 8, 2026 18:43
…slations_july-08-2026-18-43-12797590

PTC accessibility-checker (1985): Automatic Translations July 08, 2026 18:43:12797590
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1c6f10f6-bfbf-41b0-b96b-4cd1d4209e71

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/1.46.0

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread scripts/prep_release.sh Outdated

# 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')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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')

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +18 to +19
const alt = node.getAttribute( 'alt' );
return alt !== null && alt.trim() !== '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
const alt = node.getAttribute( 'alt' );
return alt !== null && alt.trim() !== '';
const alt = node.getAttribute( 'alt' );
return typeof alt === 'string' && alt.trim() !== '';

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +47 to +71
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();
} );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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();
	} );

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

pattonwebz and others added 2 commits July 8, 2026 19:55
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pattonwebz
pattonwebz requested a review from SteveJonesDev July 8, 2026 20:09
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>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

✅ Accessibility Checker build (primary only)

@github-actions github-actions Bot removed the gha-build label Jul 8, 2026
@pattonwebz
pattonwebz merged commit 29f381f into main Jul 8, 2026
27 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 8, 2026
17 tasks
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.

3 participants