diff --git a/bibliography-builder.php b/bibliography-builder.php index 080ef3f..964b880 100644 --- a/bibliography-builder.php +++ b/bibliography-builder.php @@ -1804,50 +1804,97 @@ function bibliography_builder_block_init() { * active. If BAC is not present, this function is never called. * * Registered checks: - * - empty_bibliography (error) — block has no citations. - * - heading_missing (warning) — headingText is blank; no visible heading. + * - empty_bibliography (error) - block has no citations. + * - heading_missing (warning) - citations exist with no visible heading. + * - raw_url_link_text (warning) - citation link text is a raw URL/DOI. + * - all_metadata_disabled (warning) - metadata outputs are disabled. * - * @param object $registry BAC check registry instance. * @since 1.1.0 */ -function bibliography_builder_register_a11y_checks( $registry ) { - if ( ! is_object( $registry ) || ! method_exists( $registry, 'register_check' ) ) { +function bibliography_builder_register_a11y_checks() { + if ( ! function_exists( 'ba11yc_register_block_check' ) ) { return; } - $heading_missing_message = __( + $raw_url_link_text_message = __( + 'One or more citations use a URL or DOI as link text. Add a descriptive title for screen readers.', + 'borges-bibliography-builder' + ); + $all_metadata_disabled_message = __( + 'All machine-readable metadata outputs are disabled. Enable JSON-LD, COinS, or CSL-JSON for interoperability.', + 'borges-bibliography-builder' + ); + $heading_missing_message = __( 'Bibliography block has no heading. Screen reader users may not find this section.', 'borges-bibliography-builder' ); - $registry->register_check( + \ba11yc_register_block_check( 'bibliography-builder/bibliography', - 'empty_bibliography', array( - 'error_msg' => __( 'Bibliography block contains no citations.', 'borges-bibliography-builder' ), - 'warning_msg' => __( 'Bibliography block contains no citations.', 'borges-bibliography-builder' ), - 'description' => __( 'Add at least one citation before publishing.', 'borges-bibliography-builder' ), - 'type' => 'error', - 'category' => 'accessibility', + 'namespace' => 'borges-bibliography-builder', + 'name' => 'empty_bibliography', + 'error_msg' => __( 'Bibliography block contains no citations.', 'borges-bibliography-builder' ), + 'warning_msg' => __( 'Bibliography block contains no citations.', 'borges-bibliography-builder' ), + 'description' => __( 'Add at least one citation before publishing.', 'borges-bibliography-builder' ), + 'level' => 'error', + 'configurable' => true, + 'category' => 'accessibility', ) ); - $registry->register_check( + \ba11yc_register_block_check( 'bibliography-builder/bibliography', - 'heading_missing', array( - 'error_msg' => $heading_missing_message, - 'warning_msg' => $heading_missing_message, - 'description' => __( + 'namespace' => 'borges-bibliography-builder', + 'name' => 'heading_missing', + 'error_msg' => $heading_missing_message, + 'warning_msg' => $heading_missing_message, + 'description' => __( 'Add a heading in block settings so the bibliography is announced as a document section.', 'borges-bibliography-builder' ), - 'type' => 'warning', - 'category' => 'accessibility', + 'level' => 'warning', + 'configurable' => true, + 'category' => 'accessibility', + ) + ); + + \ba11yc_register_block_check( + 'bibliography-builder/bibliography', + array( + 'namespace' => 'borges-bibliography-builder', + 'name' => 'raw_url_link_text', + 'error_msg' => $raw_url_link_text_message, + 'warning_msg' => $raw_url_link_text_message, + 'description' => __( + 'Citations with a URL or DOI should have a descriptive title as link text.', + 'borges-bibliography-builder' + ), + 'level' => 'warning', + 'configurable' => true, + 'category' => 'accessibility', + ) + ); + + \ba11yc_register_block_check( + 'bibliography-builder/bibliography', + array( + 'namespace' => 'borges-bibliography-builder', + 'name' => 'all_metadata_disabled', + 'error_msg' => $all_metadata_disabled_message, + 'warning_msg' => $all_metadata_disabled_message, + 'description' => __( + 'Enable at least one metadata output to improve citation-manager interoperability.', + 'borges-bibliography-builder' + ), + 'level' => 'warning', + 'configurable' => true, + 'category' => 'accessibility', ) ); } -add_action( 'ba11yc_ready', 'bibliography_builder_register_a11y_checks' ); +add_action( 'ba11yc_ready', 'bibliography_builder_register_a11y_checks', 10, 0 ); /** * Conditionally enqueue the BAC validation script in the block editor. diff --git a/package-lock.json b/package-lock.json index 813923f..6fb69c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13609,7 +13609,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "iconv-lite": "^0.6.2" @@ -16588,7 +16588,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -23195,7 +23195,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/sass": { @@ -25646,21 +25646,6 @@ "dev": true, "license": "MIT" }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", diff --git a/src/index.js b/src/index.js index 70655f1..50472f8 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,6 @@ import { DEFAULT_CITATION_STYLE, getDefaultHeadingText, } from './lib/formatting'; -import { registerBACChecks } from './lib/block-accessibility-checks'; registerBlockType(metadata.name, { edit: Edit, @@ -26,5 +25,3 @@ registerBlockType(metadata.name, { }, ], }); - -registerBACChecks(); diff --git a/src/lib/block-accessibility-checks.js b/src/lib/block-accessibility-checks.js index b87b5f5..6dd03ea 100644 --- a/src/lib/block-accessibility-checks.js +++ b/src/lib/block-accessibility-checks.js @@ -1,83 +1,3 @@ -const BLOCK_NAME = 'bibliography-builder/bibliography'; - -const RAW_URL_RE = /^https?:\/\/\S+$|^10\.\d{4,}\/\S+$/i; - -function isRawUrlText(text) { - return typeof text === 'string' && RAW_URL_RE.test(text.trim()); -} - -function hasRawUrlLinks(citations) { - if (!Array.isArray(citations)) { - return false; - } - return citations.some((csl) => { - const url = csl?.URL || csl?.DOI; - const title = csl?.title; - if (!url) { - return false; - } - return !title || isRawUrlText(title); - }); -} - -const checks = [ - { - id: 'empty_bibliography', - message: - 'This bibliography block has no citations. Empty blocks are omitted from the published page.', - severity: 'error', - test(attributes) { - const { citations } = attributes; - return !Array.isArray(citations) || citations.length === 0; - }, - }, - { - id: 'heading_missing', - message: - 'This bibliography block has citations but no visible heading or accessible label. Add a heading so readers can identify the reference list.', - severity: 'warning', - test(attributes) { - const { citations, headingText } = attributes; - const hasCitations = - Array.isArray(citations) && citations.length > 0; - const hasHeading = - typeof headingText === 'string' && headingText.trim() !== ''; - return hasCitations && !hasHeading; - }, - }, - { - id: 'raw_url_link_text', - message: - 'One or more citations have a URL or DOI as the only identifier. A descriptive title helps screen reader users understand the link destination.', - severity: 'warning', - test(attributes) { - return hasRawUrlLinks(attributes.citations); - }, - }, - { - id: 'all_metadata_disabled', - message: - 'All machine-readable metadata outputs (JSON-LD, COinS, CSL-JSON) are disabled. Enabling at least one improves citation-manager interoperability.', - severity: 'warning', - test(attributes) { - const { outputJsonLd, outputCoins, outputCslJson } = attributes; - return !outputJsonLd && !outputCoins && !outputCslJson; - }, - }, -]; - -export function registerBACChecks() { - if ( - typeof window === 'undefined' || - typeof window.BlockAccessibilityChecks === 'undefined' - ) { - return; - } - - const { registerBlockChecks } = window.BlockAccessibilityChecks; - if (typeof registerBlockChecks !== 'function') { - return; - } - - registerBlockChecks(BLOCK_NAME, checks); -} +// BAC v3 window-global integration path removed in v4. +// All checks are now registered via ba11yc_register_block_check() in PHP +// and validated via the ba11yc.validateBlock filter in src/validation.js. diff --git a/src/lib/block-accessibility-checks.test.js b/src/lib/block-accessibility-checks.test.js index 8fb8982..a301315 100644 --- a/src/lib/block-accessibility-checks.test.js +++ b/src/lib/block-accessibility-checks.test.js @@ -1,247 +1,5 @@ -import { registerBACChecks } from './block-accessibility-checks'; +import * as legacyIntegration from './block-accessibility-checks'; -const BLOCK_NAME = 'bibliography-builder/bibliography'; - -function makeAttributes(overrides = {}) { - return { - citations: [], - headingText: '', - outputJsonLd: true, - outputCoins: false, - outputCslJson: false, - ...overrides, - }; -} - -describe('registerBACChecks', () => { - let originalBAC; - - beforeEach(() => { - originalBAC = window.BlockAccessibilityChecks; - }); - - afterEach(() => { - window.BlockAccessibilityChecks = originalBAC; - }); - - it('is a no-op when window.BlockAccessibilityChecks is absent', () => { - delete window.BlockAccessibilityChecks; - expect(() => registerBACChecks()).not.toThrow(); - }); - - it('is a no-op when registerBlockChecks is not a function', () => { - window.BlockAccessibilityChecks = {}; - expect(() => registerBACChecks()).not.toThrow(); - }); - - it('calls registerBlockChecks with the block name and checks array', () => { - const registerBlockChecks = jest.fn(); - window.BlockAccessibilityChecks = { registerBlockChecks }; - - registerBACChecks(); - - expect(registerBlockChecks).toHaveBeenCalledTimes(1); - const [name, checks] = registerBlockChecks.mock.calls[0]; - expect(name).toBe(BLOCK_NAME); - expect(Array.isArray(checks)).toBe(true); - expect(checks.length).toBeGreaterThan(0); - }); -}); - -describe('BAC check logic', () => { - let checks; - - beforeEach(() => { - const registerBlockChecks = jest.fn(); - window.BlockAccessibilityChecks = { registerBlockChecks }; - registerBACChecks(); - checks = registerBlockChecks.mock.calls[0][1]; - }); - - afterEach(() => { - delete window.BlockAccessibilityChecks; - }); - - function getCheck(id) { - return checks.find((c) => c.id === id); - } - - describe('empty_bibliography', () => { - it('fires when citations is empty', () => { - expect(getCheck('empty_bibliography').test(makeAttributes())).toBe( - true - ); - }); - - it('fires when citations is missing', () => { - expect( - getCheck('empty_bibliography').test( - makeAttributes({ citations: undefined }) - ) - ).toBe(true); - }); - - it('does not fire when citations are present', () => { - expect( - getCheck('empty_bibliography').test( - makeAttributes({ citations: [{ title: 'Test' }] }) - ) - ).toBe(false); - }); - - it('has error severity', () => { - expect(getCheck('empty_bibliography').severity).toBe('error'); - }); - }); - - describe('heading_missing', () => { - it('fires when citations exist but headingText is empty', () => { - expect( - getCheck('heading_missing').test( - makeAttributes({ - citations: [{ title: 'Test' }], - headingText: '', - }) - ) - ).toBe(true); - }); - - it('does not fire when headingText is set', () => { - expect( - getCheck('heading_missing').test( - makeAttributes({ - citations: [{ title: 'Test' }], - headingText: 'References', - }) - ) - ).toBe(false); - }); - - it('does not fire when block is empty', () => { - expect( - getCheck('heading_missing').test( - makeAttributes({ citations: [] }) - ) - ).toBe(false); - }); - - it('has warning severity', () => { - expect(getCheck('heading_missing').severity).toBe('warning'); - }); - }); - - describe('raw_url_link_text', () => { - it('fires when a citation has a URL but no title', () => { - expect( - getCheck('raw_url_link_text').test( - makeAttributes({ - citations: [{ URL: 'https://example.com/paper' }], - }) - ) - ).toBe(true); - }); - - it('fires when a citation has a DOI but no title', () => { - expect( - getCheck('raw_url_link_text').test( - makeAttributes({ - citations: [{ DOI: '10.1234/example' }], - }) - ) - ).toBe(true); - }); - - it('fires when a citation title is itself a raw URL', () => { - expect( - getCheck('raw_url_link_text').test( - makeAttributes({ - citations: [ - { - URL: 'https://example.com', - title: 'https://example.com', - }, - ], - }) - ) - ).toBe(true); - }); - - it('does not fire when citations have descriptive titles', () => { - expect( - getCheck('raw_url_link_text').test( - makeAttributes({ - citations: [ - { URL: 'https://example.com', title: 'My Paper' }, - ], - }) - ) - ).toBe(false); - }); - - it('does not fire when citations array is empty', () => { - expect( - getCheck('raw_url_link_text').test( - makeAttributes({ citations: [] }) - ) - ).toBe(false); - }); - - it('has warning severity', () => { - expect(getCheck('raw_url_link_text').severity).toBe('warning'); - }); - }); - - describe('all_metadata_disabled', () => { - it('fires when all three metadata outputs are disabled', () => { - expect( - getCheck('all_metadata_disabled').test( - makeAttributes({ - outputJsonLd: false, - outputCoins: false, - outputCslJson: false, - }) - ) - ).toBe(true); - }); - - it('does not fire when outputJsonLd is enabled', () => { - expect( - getCheck('all_metadata_disabled').test( - makeAttributes({ - outputJsonLd: true, - outputCoins: false, - outputCslJson: false, - }) - ) - ).toBe(false); - }); - - it('does not fire when outputCoins is enabled', () => { - expect( - getCheck('all_metadata_disabled').test( - makeAttributes({ - outputJsonLd: false, - outputCoins: true, - outputCslJson: false, - }) - ) - ).toBe(false); - }); - - it('does not fire when outputCslJson is enabled', () => { - expect( - getCheck('all_metadata_disabled').test( - makeAttributes({ - outputJsonLd: false, - outputCoins: false, - outputCslJson: true, - }) - ) - ).toBe(false); - }); - - it('has warning severity', () => { - expect(getCheck('all_metadata_disabled').severity).toBe('warning'); - }); - }); +test('BAC v3 window-global path removed', () => { + expect(legacyIntegration.registerBACChecks).toBeUndefined(); }); diff --git a/src/validation.js b/src/validation.js index 5ceeb9b..cad6af9 100644 --- a/src/validation.js +++ b/src/validation.js @@ -7,8 +7,10 @@ * present, and the filter is a no-op if the framework never calls it. * * Checks registered: - * - empty_bibliography (error) — block has no citations - * - heading_missing (warning) — headingText is blank; no heading visible + * - empty_bibliography (error) — block has no citations + * - heading_missing (warning) — headingText is blank; no heading visible + * - raw_url_link_text (warning) — citation uses a raw URL/DOI as link text + * - all_metadata_disabled (warning) — all metadata outputs are turned off * * @see https://github.com/troychaplin/block-accessibility-checks * @since 1.1.0 @@ -18,6 +20,26 @@ import { addFilter } from '@wordpress/hooks'; const BLOCK_TYPE = 'bibliography-builder/bibliography'; const NAMESPACE = 'borges-bibliography-builder/bac-validation'; +const RAW_URL_RE = /^https?:\/\/\S+$|^10\.\d{4,}\/\S+$/i; + +function isRawUrlText(text) { + return typeof text === 'string' && RAW_URL_RE.test(text.trim()); +} + +function hasRawUrlLinks(citations) { + if (!Array.isArray(citations)) { + return false; + } + return citations.some((csl) => { + const url = csl?.URL || csl?.DOI; + const title = csl?.title; + if (!url) { + return false; + } + return !title || isRawUrlText(title); + }); +} + /** * Pure validation logic — exported for unit testing. * @@ -34,10 +56,22 @@ export function validateBibliographyBlock(attributes, checkName) { } if (checkName === 'heading_missing') { - return ( + const hasCitations = + Array.isArray(attributes.citations) && + attributes.citations.length > 0; + const hasHeading = typeof attributes.headingText === 'string' && - attributes.headingText.trim().length > 0 - ); + attributes.headingText.trim().length > 0; + return !hasCitations || hasHeading; + } + + if (checkName === 'raw_url_link_text') { + return !hasRawUrlLinks(attributes.citations); + } + + if (checkName === 'all_metadata_disabled') { + const { outputJsonLd, outputCoins, outputCslJson } = attributes; + return !!(outputJsonLd || outputCoins || outputCslJson); } // Unknown checks pass through unchanged. @@ -65,4 +99,4 @@ export function bacValidateBlock(isValid, blockType, attributes, checkName) { return validateBibliographyBlock(attributes, checkName); } -addFilter('ba11yc_validate_block', NAMESPACE, bacValidateBlock); +addFilter('ba11yc.validateBlock', NAMESPACE, bacValidateBlock); diff --git a/src/validation.test.js b/src/validation.test.js index ddc4a7c..2d5734b 100644 --- a/src/validation.test.js +++ b/src/validation.test.js @@ -9,9 +9,9 @@ jest.mock('@wordpress/hooks', () => ({ })); describe('BAC filter registration', () => { - it('registers the documented BAC validation filter', () => { + it('registers the ba11yc.validateBlock filter (dot-separated, v4 API)', () => { expect(addFilter).toHaveBeenCalledWith( - 'ba11yc_validate_block', + 'ba11yc.validateBlock', 'borges-bibliography-builder/bac-validation', bacValidateBlock ); @@ -67,37 +67,174 @@ describe('validateBibliographyBlock', () => { it('passes when headingText is a non-empty string', () => { expect( validateBibliographyBlock( - { headingText: 'References' }, + { + citations: [{ csl: { title: 'Example' } }], + headingText: 'References', + }, 'heading_missing' ) ).toBe(true); }); - it('fails when headingText is an empty string', () => { + it('passes when headingText is empty and the block has no citations', () => { expect( validateBibliographyBlock( - { headingText: '' }, + { citations: [], headingText: '' }, + 'heading_missing' + ) + ).toBe(true); + }); + + it('passes when citations attribute is missing', () => { + expect(validateBibliographyBlock({}, 'heading_missing')).toBe(true); + }); + + it('fails when citations exist and headingText is an empty string', () => { + expect( + validateBibliographyBlock( + { + citations: [{ csl: { title: 'Example' } }], + headingText: '', + }, 'heading_missing' ) ).toBe(false); }); - it('fails when headingText is missing', () => { - expect(validateBibliographyBlock({}, 'heading_missing')).toBe( - false - ); + it('fails when citations exist and headingText is missing', () => { + expect( + validateBibliographyBlock( + { citations: [{ csl: { title: 'Example' } }] }, + 'heading_missing' + ) + ).toBe(false); }); - it('fails when headingText is whitespace-only', () => { + it('fails when citations exist and headingText is whitespace-only', () => { expect( validateBibliographyBlock( - { headingText: ' ' }, + { + citations: [{ csl: { title: 'Example' } }], + headingText: ' ', + }, 'heading_missing' ) ).toBe(false); }); }); + describe('raw_url_link_text check', () => { + it('passes when citations have descriptive titles', () => { + expect( + validateBibliographyBlock( + { + citations: [ + { URL: 'https://example.com', title: 'My Paper' }, + ], + }, + 'raw_url_link_text' + ) + ).toBe(true); + }); + + it('fails when a citation has a URL but no title', () => { + expect( + validateBibliographyBlock( + { citations: [{ URL: 'https://example.com/paper' }] }, + 'raw_url_link_text' + ) + ).toBe(false); + }); + + it('fails when a citation has a DOI but no title', () => { + expect( + validateBibliographyBlock( + { citations: [{ DOI: '10.1234/example' }] }, + 'raw_url_link_text' + ) + ).toBe(false); + }); + + it('fails when a citation title is itself a raw URL', () => { + expect( + validateBibliographyBlock( + { + citations: [ + { + URL: 'https://example.com', + title: 'https://example.com', + }, + ], + }, + 'raw_url_link_text' + ) + ).toBe(false); + }); + + it('passes when citations array is empty', () => { + expect( + validateBibliographyBlock( + { citations: [] }, + 'raw_url_link_text' + ) + ).toBe(true); + }); + }); + + describe('all_metadata_disabled check', () => { + it('fails when all three metadata outputs are disabled', () => { + expect( + validateBibliographyBlock( + { + outputJsonLd: false, + outputCoins: false, + outputCslJson: false, + }, + 'all_metadata_disabled' + ) + ).toBe(false); + }); + + it('passes when outputJsonLd is enabled', () => { + expect( + validateBibliographyBlock( + { + outputJsonLd: true, + outputCoins: false, + outputCslJson: false, + }, + 'all_metadata_disabled' + ) + ).toBe(true); + }); + + it('passes when outputCoins is enabled', () => { + expect( + validateBibliographyBlock( + { + outputJsonLd: false, + outputCoins: true, + outputCslJson: false, + }, + 'all_metadata_disabled' + ) + ).toBe(true); + }); + + it('passes when outputCslJson is enabled', () => { + expect( + validateBibliographyBlock( + { + outputJsonLd: false, + outputCoins: false, + outputCslJson: true, + }, + 'all_metadata_disabled' + ) + ).toBe(true); + }); + }); + describe('unknown check name', () => { it('returns true (passes) for unrecognised check names', () => { expect(validateBibliographyBlock({}, 'some_future_check')).toBe( diff --git a/tests/e2e/a11y.spec.js b/tests/e2e/a11y.spec.js index 7d1e880..a572aea 100644 --- a/tests/e2e/a11y.spec.js +++ b/tests/e2e/a11y.spec.js @@ -544,10 +544,15 @@ test.describe('Bibliography block accessibility gate', () => { await dismissEditorOverlay(page); // BAC may not be active in all environments; skip gracefully if absent. - // The plugin exposes a global `window.BlockAccessibilityChecks` when loaded. + // Detect via the block-accessibility-checks Redux store (v4 API). const bacPresent = await page .evaluate( - () => typeof window.BlockAccessibilityChecks !== 'undefined' + () => + typeof window.wp !== 'undefined' && + typeof window.wp.data !== 'undefined' && + typeof window.wp.data.select( + 'block-accessibility-checks' + ) !== 'undefined' ) .catch(() => false); diff --git a/tests/phpunit/A11yIntegrationTest.php b/tests/phpunit/A11yIntegrationTest.php index 41fc97d..d78f9cf 100644 --- a/tests/phpunit/A11yIntegrationTest.php +++ b/tests/phpunit/A11yIntegrationTest.php @@ -2,18 +2,6 @@ use PHPUnit\Framework\TestCase; -final class BibliographyBuilderFakeA11yRegistry { - public $checks = array(); - - public function register_check( $block_name, $check_name, $args ) { - $this->checks[] = array( - 'block_name' => $block_name, - 'check_name' => $check_name, - 'args' => $args, - ); - } -} - final class A11yIntegrationTest extends TestCase { protected function setUp(): void { @@ -21,35 +9,89 @@ protected function setUp(): void { bibliography_builder_test_reset_state(); } - public function test_register_a11y_checks_ignores_invalid_registry(): void { - $this->assertNull( bibliography_builder_register_a11y_checks( null ) ); + // ------------------------------------------------------------------------- + // PHP registration + // ------------------------------------------------------------------------- + + public function test_register_a11y_checks_registers_all_four_checks(): void { + bibliography_builder_register_a11y_checks(); - $result = bibliography_builder_register_a11y_checks( new stdClass() ); + $calls = $GLOBALS['bibliography_builder_test_bac_register_calls'] ?? array(); - $this->assertNull( $result ); + $this->assertCount( 4, $calls ); + + $ids = array_column( array_column( $calls, 'args' ), 'name' ); + $this->assertContains( 'empty_bibliography', $ids ); + $this->assertContains( 'heading_missing', $ids ); + $this->assertContains( 'raw_url_link_text', $ids ); + $this->assertContains( 'all_metadata_disabled', $ids ); } - public function test_register_a11y_checks_registers_bibliography_checks(): void { - $registry = new BibliographyBuilderFakeA11yRegistry(); - - bibliography_builder_register_a11y_checks( $registry ); - - $this->assertCount( 2, $registry->checks ); - $this->assertSame( 'bibliography-builder/bibliography', $registry->checks[0]['block_name'] ); - $this->assertSame( 'empty_bibliography', $registry->checks[0]['check_name'] ); - $this->assertSame( 'error', $registry->checks[0]['args']['type'] ); - $this->assertSame( 'accessibility', $registry->checks[0]['args']['category'] ); - $this->assertSame( 'bibliography-builder/bibliography', $registry->checks[1]['block_name'] ); - $this->assertSame( 'heading_missing', $registry->checks[1]['check_name'] ); - $this->assertSame( 'warning', $registry->checks[1]['args']['type'] ); - $this->assertSame( 'accessibility', $registry->checks[1]['args']['category'] ); - $this->assertNotEmpty( $registry->checks[1]['args']['error_msg'] ); - $this->assertSame( - $registry->checks[1]['args']['error_msg'], - $registry->checks[1]['args']['warning_msg'] + public function test_register_a11y_checks_uses_correct_block_type(): void { + bibliography_builder_register_a11y_checks(); + + $calls = $GLOBALS['bibliography_builder_test_bac_register_calls'] ?? array(); + + foreach ( $calls as $call ) { + $this->assertSame( 'bibliography-builder/bibliography', $call['block_type'] ); + } + } + + public function test_register_a11y_checks_uses_v4_args_shape(): void { + bibliography_builder_register_a11y_checks(); + + $calls = $GLOBALS['bibliography_builder_test_bac_register_calls'] ?? array(); + + foreach ( $calls as $call ) { + $args = $call['args']; + $this->assertArrayHasKey( 'namespace', $args, 'namespace key required in v4' ); + $this->assertSame( 'borges-bibliography-builder', $args['namespace'] ); + $this->assertArrayHasKey( 'level', $args, 'level key required in v4' ); + $this->assertArrayNotHasKey( 'type', $args, 'type key removed in v4' ); + $this->assertNotEmpty( $args['error_msg'] ); + } + } + + public function test_empty_bibliography_check_is_error(): void { + bibliography_builder_register_a11y_checks(); + + $calls = $GLOBALS['bibliography_builder_test_bac_register_calls'] ?? array(); + $check = $this->findCheck( $calls, 'empty_bibliography' ); + + $this->assertSame( 'error', $check['args']['level'] ); + } + + public function test_heading_missing_check_is_warning(): void { + bibliography_builder_register_a11y_checks(); + + $calls = $GLOBALS['bibliography_builder_test_bac_register_calls'] ?? array(); + $check = $this->findCheck( $calls, 'heading_missing' ); + + $this->assertSame( 'warning', $check['args']['level'] ); + $this->assertNotEmpty( $check['args']['error_msg'] ); + $this->assertSame( $check['args']['error_msg'], $check['args']['warning_msg'] ); + } + + public function test_bac_ready_action_passes_no_arguments(): void { + $matching_actions = array_filter( + $GLOBALS['bibliography_builder_test_added_actions'], + static function ( $action ) { + return 'ba11yc_ready' === $action['hook_name'] + && 'bibliography_builder_register_a11y_checks' === $action['callback']; + } ); + + $this->assertNotEmpty( $matching_actions ); + + $action = array_values( $matching_actions )[0]; + + $this->assertSame( 0, $action['accepted_args'] ); } + // ------------------------------------------------------------------------- + // Enqueue behaviour (unchanged from v3 — handle already correct) + // ------------------------------------------------------------------------- + public function test_enqueue_a11y_validation_is_noop_when_bac_script_is_absent(): void { bibliography_builder_enqueue_a11y_validation(); @@ -143,4 +185,17 @@ public function test_block_init_registers_block_metadata_directory(): void { $GLOBALS['bibliography_builder_test_registered_block_types'] ); } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private function findCheck( array $calls, string $name ): array { + foreach ( $calls as $call ) { + if ( ( $call['args']['name'] ?? '' ) === $name ) { + return $call; + } + } + $this->fail( "Check '{$name}' was not registered." ); + } } diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 66a54bd..8f4bec3 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -19,6 +19,7 @@ $GLOBALS['bibliography_builder_test_transients'] = array(); $GLOBALS['bibliography_builder_test_object_cache'] = array(); $GLOBALS['bibliography_builder_test_using_ext_object_cache'] = false; +$GLOBALS['bibliography_builder_test_bac_register_calls'] = array(); function bibliography_builder_test_reset_state() { $GLOBALS['bibliography_builder_test_posts'] = array(); @@ -35,6 +36,7 @@ function bibliography_builder_test_reset_state() { $GLOBALS['bibliography_builder_test_transients'] = array(); $GLOBALS['bibliography_builder_test_object_cache'] = array(); $GLOBALS['bibliography_builder_test_using_ext_object_cache'] = false; + $GLOBALS['bibliography_builder_test_bac_register_calls'] = array(); } function bibliography_builder_test_set_post( $post_id, $status, $content, $password_required = false ) { @@ -122,6 +124,13 @@ function add_action( $hook_name = '', $callback = null, $priority = 10, $accepte function add_filter() {} +function ba11yc_register_block_check( $block_type, $args ) { + $GLOBALS['bibliography_builder_test_bac_register_calls'][] = array( + 'block_type' => $block_type, + 'args' => $args, + ); +} + function register_block_type( $block_type ) { $GLOBALS['bibliography_builder_test_registered_block_types'][] = $block_type; }