feat(v3.2.7): expand WCAG ruleset to 127 rules, add HTML syntax linter, single-pass rule engine, template preprocessor, and accessibility analytics

This commit is contained in:
Enes Yakıştır
2026-08-16 22:00:30 +03:00
parent 9a6784ad19
commit 2de1f34878
93 changed files with 4879 additions and 305 deletions
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AccesskeyDuplicate;
class AccesskeyDuplicateTest extends TestCase
{
public function testFlagsDuplicateAccesskeys(): void
{
$scanner = new Scanner();
$scanner->addRule(new AccesskeyDuplicate());
$violations = $scanner->scan('<div><a href="/" accesskey="h">Home</a><a href="/help" accesskey="h">Help</a></div>');
$this->assertCount(2, $violations);
$this->assertSame('WCAG_2_1_1_ACCESSKEY_DUPLICATE', $violations[0]->ruleId);
}
public function testPassesUniqueAccesskeys(): void
{
$scanner = new Scanner();
$scanner->addRule(new AccesskeyDuplicate());
$violations = $scanner->scan('<div><a href="/" accesskey="h">Home</a><a href="/help" accesskey="p">Help</a></div>');
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AriaBrailleEquivalent;
class AriaBrailleEquivalentTest extends TestCase
{
public function testFlagsBrailleLabelWithoutStandardName(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaBrailleEquivalent());
$violations = $scanner->scan('<div><button aria-braillelabel="btn"></button></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_BRAILLE', $violations[0]->ruleId);
}
public function testPassesBrailleLabelWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaBrailleEquivalent());
$violations = $scanner->scan('<div><button aria-braillelabel="btn" aria-label="Submit Form">Submit</button></div>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AriaCurrentValid;
class AriaCurrentValidTest extends TestCase
{
public function testFlagsInvalidAriaCurrent(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaCurrentValid());
$violations = $scanner->scan('<div><a href="/home" aria-current="active">Home</a></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_CURRENT', $violations[0]->ruleId);
}
public function testPassesValidAriaCurrent(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaCurrentValid());
$violations = $scanner->scan('<div><a href="/home" aria-current="page">Home</a></div>');
$this->assertCount(0, $violations);
}
public function testPassesBooleanAriaCurrent(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaCurrentValid());
$violations = $scanner->scan('<div><a href="/step2" aria-current="true">Step 2</a></div>');
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AriaDisabledFocusable;
class AriaDisabledFocusableTest extends TestCase
{
public function testFlagsDisabledWithPositiveTabindex(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaDisabledFocusable());
$violations = $scanner->scan('<div><button disabled tabindex="0">Disabled Button</button></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_DISABLED', $violations[0]->ruleId);
}
public function testPassesStandardDisabled(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaDisabledFocusable());
$violations = $scanner->scan('<div><button disabled>Disabled Button</button></div>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AriaErrorMessage;
class AriaErrorMessageTest extends TestCase
{
public function testFlagsMissingTargetElement(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaErrorMessage());
$violations = $scanner->scan('<div><input id="email" aria-invalid="true" aria-errormessage="nonexistent_err"></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_3_3_1_ARIA_ERRORMESSAGE', $violations[0]->ruleId);
}
public function testFlagsMissingAriaInvalid(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaErrorMessage());
$violations = $scanner->scan('<div><input id="email" aria-errormessage="email_err"><span id="email_err">Error</span></div>');
$this->assertCount(1, $violations);
}
public function testPassesValidErrorMessageSetup(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaErrorMessage());
$violations = $scanner->scan('<div><input id="email" aria-invalid="true" aria-errormessage="email_err"><span id="email_err">Invalid Email Address</span></div>');
$this->assertCount(0, $violations);
}
}
@@ -0,0 +1,33 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AriaLabelMisuseOnGeneric;
class AriaLabelMisuseOnGenericTest extends TestCase
{
public function testFlagsGenericDivWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaLabelMisuseOnGeneric());
$html = '<div><div aria-label="Card container">Content</div></div>';
$violations = $scanner->scan($html);
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_LABEL_GENERIC', $violations[0]->ruleId);
}
public function testPassesDivWithRoleAndAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaLabelMisuseOnGeneric());
$html = '<div><div role="region" aria-label="Card container">Content</div></div>';
$violations = $scanner->scan($html);
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AriaProhibitedAttr;
class AriaProhibitedAttrTest extends TestCase
{
public function testFlagsAriaLabelOnPresentationRole(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaProhibitedAttr());
$violations = $scanner->scan('<div><div role="presentation" aria-label="Decorative block">Content</div></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_PROHIBITED_ATTR', $violations[0]->ruleId);
}
public function testFlagsAriaLabelledbyOnNoneRole(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaProhibitedAttr());
$violations = $scanner->scan('<div><div role="none" aria-labelledby="some_id">Content</div></div>');
$this->assertCount(1, $violations);
}
public function testPassesValidRolesWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaProhibitedAttr());
$violations = $scanner->scan('<div><div role="region" aria-label="Main Section">Content</div></div>');
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AriaRedundantRole;
class AriaRedundantRoleTest extends TestCase
{
public function testFlagsRedundantRoleOnNav(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaRedundantRole());
$violations = $scanner->scan('<div><nav role="navigation"><a href="/">Home</a></nav></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_REDUNDANT_ROLE', $violations[0]->ruleId);
}
public function testPassesNavWithoutRedundantRole(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaRedundantRole());
$violations = $scanner->scan('<div><nav><a href="/">Home</a></nav></div>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\AriaRoledescription;
class AriaRoledescriptionTest extends TestCase
{
public function testFlagsEmptyRoledescription(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaRoledescription());
$violations = $scanner->scan('<div><div role="region" aria-roledescription="">Content</div></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_ROLEDESCRIPTION', $violations[0]->ruleId);
}
public function testFlagsRoledescriptionOnPresentation(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaRoledescription());
$violations = $scanner->scan('<div><div role="presentation" aria-roledescription="slide">Content</div></div>');
$this->assertCount(1, $violations);
}
public function testPassesValidRoledescription(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaRoledescription());
$violations = $scanner->scan('<div><div role="region" aria-roledescription="slide" aria-label="Slide 1">Slide Content</div></div>');
$this->assertCount(0, $violations);
}
}
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\ButtonNestedInteractive;
class ButtonNestedInteractiveTest extends TestCase
{
public function testFlagsNestedAnchorInsideButton(): void
{
$scanner = new Scanner();
$scanner->addRule(new ButtonNestedInteractive());
$violations = $scanner->scan('<div><button><a href="/cart">Cart</a></button></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_BUTTON_NESTED_INTERACTIVE', $violations[0]->ruleId);
}
public function testFlagsNestedButtonInsideButton(): void
{
$scanner = new Scanner();
$scanner->addRule(new ButtonNestedInteractive());
$violations = $scanner->scan('<div><button><button>Sub action</button></button></div>');
$this->assertCount(1, $violations);
}
public function testPassesStandardButton(): void
{
$scanner = new Scanner();
$scanner->addRule(new ButtonNestedInteractive());
$violations = $scanner->scan('<div><button><span>Standard Button</span></button></div>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\DetailsMissingSummary;
class DetailsMissingSummaryTest extends TestCase
{
public function testFlagsDetailsWithoutSummary(): void
{
$scanner = new Scanner();
$scanner->addRule(new DetailsMissingSummary());
$violations = $scanner->scan('<details><p>Some hidden details here</p></details>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_DETAILS_SUMMARY', $violations[0]->ruleId);
}
public function testFlagsDetailsWithEmptySummary(): void
{
$scanner = new Scanner();
$scanner->addRule(new DetailsMissingSummary());
$violations = $scanner->scan('<details><summary></summary><p>Details</p></details>');
$this->assertCount(1, $violations);
}
public function testPassesDetailsWithValidSummary(): void
{
$scanner = new Scanner();
$scanner->addRule(new DetailsMissingSummary());
$violations = $scanner->scan('<details><summary>More info</summary><p>Details content</p></details>');
$this->assertCount(0, $violations);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\DraggingMovements;
class DraggingMovementsTest extends TestCase
{
public function testFlagsDraggableWithoutAlternativeControls(): void
{
$scanner = new Scanner();
$scanner->addRule(new DraggingMovements());
$html = '<div><div draggable="true" class="task-card">Task 1</div></div>';
$violations = $scanner->scan($html);
$this->assertCount(1, $violations);
$this->assertSame('WCAG_2_5_7_DRAGGING_MOVEMENTS', $violations[0]->ruleId);
}
public function testPassesDraggableWithKeyboardShortcutsOrButtons(): void
{
$scanner = new Scanner();
$scanner->addRule(new DraggingMovements());
$html = '<div><div draggable="true" aria-keyshortcuts="Control+Up Control+Down" class="task-card">Task 1</div></div>';
$violations = $scanner->scan($html);
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\DuplicateId;
class DuplicateIdTest extends TestCase
{
public function testFlagsDuplicateIds(): void
{
$scanner = new Scanner();
$scanner->addRule(new DuplicateId());
$violations = $scanner->scan('<div><div id="card">A</div><div id="card">B</div></div>');
$this->assertCount(2, $violations);
$this->assertSame('WCAG_4_1_2_DUPLICATE_ID', $violations[0]->ruleId);
}
public function testPassesUniqueIds(): void
{
$scanner = new Scanner();
$scanner->addRule(new DuplicateId());
$violations = $scanner->scan('<div><div id="card1">A</div><div id="card2">B</div></div>');
$this->assertCount(0, $violations);
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\EmptyHeading;
class EmptyHeadingTest extends TestCase
{
public function testFlagsEmptyHeading(): void
{
$scanner = new Scanner();
$scanner->addRule(new EmptyHeading());
$violations = $scanner->scan('<div><h1></h1></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_EMPTY_HEADING', $violations[0]->ruleId);
}
public function testFlagsEmptyAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new EmptyHeading());
$violations = $scanner->scan('<div><h2 aria-label=" "></h2></div>');
$this->assertCount(1, $violations);
}
public function testPassesHeadingWithImageAlt(): void
{
$scanner = new Scanner();
$scanner->addRule(new EmptyHeading());
$violations = $scanner->scan('<div><h1><img src="logo.png" alt="Company Name"></h1></div>');
$this->assertCount(0, $violations);
}
public function testPassesHeadingWithSvgTitle(): void
{
$scanner = new Scanner();
$scanner->addRule(new EmptyHeading());
$violations = $scanner->scan('<div><h1><svg><title>Dashboard</title></svg></h1></div>');
$this->assertCount(0, $violations);
}
public function testPassesHeadingWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new EmptyHeading());
$violations = $scanner->scan('<div><h1 aria-label="Accessible Title"></h1></div>');
$this->assertCount(0, $violations);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\FocusNotObscured;
class FocusNotObscuredTest extends TestCase
{
public function testFlagsFixedFullWidthTopBarWithoutScrollPadding(): void
{
$scanner = new Scanner();
$scanner->addRule(new FocusNotObscured());
$html = '<div><header style="position: fixed; top: 0; left: 0; width: 100%; height: 60px;">Navbar</header></div>';
$violations = $scanner->scan($html);
$this->assertCount(1, $violations);
$this->assertSame('WCAG_2_4_11_FOCUS_NOT_OBSCURED', $violations[0]->ruleId);
}
public function testPassesStaticElements(): void
{
$scanner = new Scanner();
$scanner->addRule(new FocusNotObscured());
$html = '<div><header style="width: 100%; height: 60px;">Navbar</header></div>';
$violations = $scanner->scan($html);
$this->assertCount(0, $violations);
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\FormLabel;
class FormLabelTest extends TestCase
{
public function testFlagsInputWithoutLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new FormLabel());
$violations = $scanner->scan('<div><input type="text" name="username"></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_LABEL', $violations[0]->ruleId);
}
public function testFlagsEmptyAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new FormLabel());
$violations = $scanner->scan('<div><input type="text" name="username" aria-label=" "></div>');
$this->assertCount(1, $violations);
}
public function testPassesInputWithAssociatedLabelFor(): void
{
$scanner = new Scanner();
$scanner->addRule(new FormLabel());
$violations = $scanner->scan('<div><label for="usr">Username</label><input type="text" id="usr" name="username"></div>');
$this->assertCount(0, $violations);
}
public function testPassesInputWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new FormLabel());
$violations = $scanner->scan('<div><input type="text" name="username" aria-label="Username"></div>');
$this->assertCount(0, $violations);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\HeadingOrder;
class HeadingOrderTest extends TestCase
{
public function testFlagsSkippedHeadingLevel(): void
{
$scanner = new Scanner();
$scanner->addRule(new HeadingOrder());
$violations = $scanner->scan('<div><h1>Main Heading</h1><h3>Skipped Subheading</h3></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_HEADING', $violations[0]->ruleId);
}
public function testPassesSequentialHeadings(): void
{
$scanner = new Scanner();
$scanner->addRule(new HeadingOrder());
$violations = $scanner->scan('<div><h1>Main Heading</h1><h2>Section</h2><h3>Sub-section</h3></div>');
$this->assertCount(0, $violations);
}
public function testIsStatelessAcrossMultipleScans(): void
{
$scanner = new Scanner();
$scanner->addRule(new HeadingOrder());
// First scan ends with H3
$scanner->scan('<div><h1>H1</h1><h2>H2</h2><h3>H3</h3></div>');
// Second scan starts with H1, should not flag jump from previous scan's H3
$violations = $scanner->scan('<div><h1>Fresh Document H1</h1><h2>H2</h2></div>');
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\HtmlDirValid;
class HtmlDirValidTest extends TestCase
{
public function testFlagsInvalidDir(): void
{
$scanner = new Scanner();
$scanner->addRule(new HtmlDirValid());
$violations = $scanner->scan('<div><p dir="left">Paragraph</p></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_2_HTML_DIR', $violations[0]->ruleId);
}
public function testPassesValidDir(): void
{
$scanner = new Scanner();
$scanner->addRule(new HtmlDirValid());
$violations = $scanner->scan('<div><p dir="rtl">Arabic text</p><p dir="ltr">English</p><p dir="auto">Auto</p></div>');
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\HtmlSyntaxValid;
class HtmlSyntaxValidTest extends TestCase
{
public function testFlagsDuplicateAttributesInHtml(): void
{
$scanner = new Scanner();
$scanner->addRule(new HtmlSyntaxValid());
$violations = $scanner->scan('<div><input id="email" type="email" id="email2"></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_1_HTML_PARSING_SYNTAX', $violations[0]->ruleId);
}
public function testPassesValidHtml(): void
{
$scanner = new Scanner();
$scanner->addRule(new HtmlSyntaxValid());
$violations = $scanner->scan('<div><input id="email" type="email" name="user_email"></div>');
$this->assertCount(0, $violations);
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\IframeTitle;
class IframeTitleTest extends TestCase
{
public function testFlagsIframeWithoutTitle(): void
{
$scanner = new Scanner();
$scanner->addRule(new IframeTitle());
$violations = $scanner->scan('<div><iframe src="map.html"></iframe></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_IFRAME_TITLE', $violations[0]->ruleId);
}
public function testPassesIframeWithTitle(): void
{
$scanner = new Scanner();
$scanner->addRule(new IframeTitle());
$violations = $scanner->scan('<div><iframe src="map.html" title="Office Location Map"></iframe></div>');
$this->assertCount(0, $violations);
}
public function testPassesIframeWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new IframeTitle());
$violations = $scanner->scan('<div><iframe src="map.html" aria-label="Interactive Map"></iframe></div>');
$this->assertCount(0, $violations);
}
public function testPassesAriaHiddenIframe(): void
{
$scanner = new Scanner();
$scanner->addRule(new IframeTitle());
$violations = $scanner->scan('<div><iframe src="tracker.html" aria-hidden="true"></iframe></div>');
$this->assertCount(0, $violations);
}
}
@@ -0,0 +1,33 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\InputAutocompleteAppropriate;
class InputAutocompleteAppropriateTest extends TestCase
{
public function testFlagsInappropriateAutocompleteType(): void
{
$scanner = new Scanner();
$scanner->addRule(new InputAutocompleteAppropriate());
$html = '<form><input type="number" autocomplete="email"></form>';
$violations = $scanner->scan($html);
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_5_AUTOCOMPLETE_APPROPRIATE', $violations[0]->ruleId);
}
public function testPassesAppropriateAutocompleteType(): void
{
$scanner = new Scanner();
$scanner->addRule(new InputAutocompleteAppropriate());
$html = '<form><input type="email" autocomplete="email"></form>';
$violations = $scanner->scan($html);
$this->assertCount(0, $violations);
}
}
+15 -5
View File
@@ -8,22 +8,32 @@ use YakNet\AccessibilityConsole\Rules\InputMissingAutocomplete;
class InputMissingAutocompleteTest extends TestCase
{
public function testFlagsPersonalDataInputWithoutAutocomplete(): void
public function testFlagsPasswordWithAutocompleteOff(): void
{
$scanner = new Scanner();
$scanner->addRule(new InputMissingAutocomplete());
$violations = $scanner->scan('<form><input type="email" name="user_email"></form>');
$violations = $scanner->scan('<form><input type="password" name="pwd" autocomplete="off"></form>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_5_INPUT_AUTOCOMPLETE', $violations[0]->ruleId);
$this->assertSame('WCAG_3_3_8_ACCESSIBLE_AUTH', $violations[0]->ruleId);
}
public function testPassesPersonalDataInputWithAutocomplete(): void
public function testFlagsPasteBlockingOnInput(): void
{
$scanner = new Scanner();
$scanner->addRule(new InputMissingAutocomplete());
$violations = $scanner->scan('<form><input type="email" name="user_email" autocomplete="email"></form>');
$violations = $scanner->scan('<form><input type="text" name="pin" onpaste="return false;"></form>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_3_3_8_ACCESSIBLE_AUTH', $violations[0]->ruleId);
}
public function testPassesAccessiblePasswordInput(): void
{
$scanner = new Scanner();
$scanner->addRule(new InputMissingAutocomplete());
$violations = $scanner->scan('<form><input type="password" name="pwd" autocomplete="current-password"></form>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\MediaControls;
class MediaControlsTest extends TestCase
{
public function testFlagsAudioWithoutControls(): void
{
$scanner = new Scanner();
$scanner->addRule(new MediaControls());
$violations = $scanner->scan('<div><audio src="audio.mp3"></audio></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_4_2_MEDIA_CONTROLS', $violations[0]->ruleId);
}
public function testPassesAudioWithControls(): void
{
$scanner = new Scanner();
$scanner->addRule(new MediaControls());
$violations = $scanner->scan('<div><audio src="audio.mp3" controls></audio></div>');
$this->assertCount(0, $violations);
}
public function testPassesBackgroundMutedAutoplayVideo(): void
{
$scanner = new Scanner();
$scanner->addRule(new MediaControls());
$violations = $scanner->scan('<div><video src="bg.mp4" muted autoplay loop></video></div>');
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\MetaCharsetPresent;
class MetaCharsetPresentTest extends TestCase
{
public function testFlagsMissingCharsetInHead(): void
{
$scanner = new Scanner();
$scanner->addRule(new MetaCharsetPresent());
$violations = $scanner->scan('<!DOCTYPE html><html><head><title>Test Page</title></head><body><h1>Hello</h1></body></html>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_3_1_1_META_CHARSET', $violations[0]->ruleId);
}
public function testPassesWhenMetaCharsetPresent(): void
{
$scanner = new Scanner();
$scanner->addRule(new MetaCharsetPresent());
$violations = $scanner->scan('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Test Page</title></head><body><h1>Hello</h1></body></html>');
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\MeterAccessibility;
class MeterAccessibilityTest extends TestCase
{
public function testFlagsMeterWithoutAccessibleName(): void
{
$scanner = new Scanner();
$scanner->addRule(new MeterAccessibility());
$violations = $scanner->scan('<div><meter value="0.6"></meter></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_METER_ACCESSIBLE', $violations[0]->ruleId);
}
public function testPassesMeterWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new MeterAccessibility());
$violations = $scanner->scan('<div><meter value="0.6" aria-label="Disk Usage"></meter></div>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\NestedInteractive;
class NestedInteractiveTest extends TestCase
{
public function testFlagsNestedLinkInsideLink(): void
{
$scanner = new Scanner();
$scanner->addRule(new NestedInteractive());
$violations = $scanner->scan('<div><a href="/outer">Outer <a href="/inner">Inner</a></a></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_NESTED_INTERACTIVE', $violations[0]->ruleId);
}
public function testFlagsButtonInsideLink(): void
{
$scanner = new Scanner();
$scanner->addRule(new NestedInteractive());
$violations = $scanner->scan('<div><a href="/outer"><button>Button</button></a></div>');
$this->assertCount(1, $violations);
}
public function testPassesNonNestedInteractives(): void
{
$scanner = new Scanner();
$scanner->addRule(new NestedInteractive());
$violations = $scanner->scan('<div><a href="/link">Link</a><button>Button</button></div>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\RoleImgAlt;
class RoleImgAltTest extends TestCase
{
public function testFlagsRoleImgWithoutAccessibleName(): void
{
$scanner = new Scanner();
$scanner->addRule(new RoleImgAlt());
$violations = $scanner->scan('<div><span role="img" class="icon-star"></span></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_1_1_ROLE_IMG', $violations[0]->ruleId);
}
public function testPassesRoleImgWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new RoleImgAlt());
$violations = $scanner->scan('<div><span role="img" class="icon-star" aria-label="Five Star Rating"></span></div>');
$this->assertCount(0, $violations);
}
public function testPassesRoleImgWithAriaHidden(): void
{
$scanner = new Scanner();
$scanner->addRule(new RoleImgAlt());
$violations = $scanner->scan('<div><span role="img" class="icon-star" aria-hidden="true"></span></div>');
$this->assertCount(0, $violations);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\SelectValidChildren;
class SelectValidChildrenTest extends TestCase
{
public function testFlagsInvalidDivInSelect(): void
{
$scanner = new Scanner();
$scanner->addRule(new SelectValidChildren());
$violations = $scanner->scan('<select><div>Option 1</div><option value="1">Option 1</option></select>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_SELECT_CHILDREN', $violations[0]->ruleId);
}
public function testPassesValidSelectOptionsAndOptgroups(): void
{
$scanner = new Scanner();
$scanner->addRule(new SelectValidChildren());
$violations = $scanner->scan('<select><optgroup label="Group"><option value="1">Opt 1</option></optgroup></select>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\TableHeaderNotEmpty;
class TableHeaderNotEmptyTest extends TestCase
{
public function testFlagsEmptyTableHeader(): void
{
$scanner = new Scanner();
$scanner->addRule(new TableHeaderNotEmpty());
$violations = $scanner->scan('<table><tr><th></th><td>Value</td></tr></table>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_TH_NOT_EMPTY', $violations[0]->ruleId);
}
public function testPassesTableHeaderWithText(): void
{
$scanner = new Scanner();
$scanner->addRule(new TableHeaderNotEmpty());
$violations = $scanner->scan('<table><tr><th>Name</th><td>John</td></tr></table>');
$this->assertCount(0, $violations);
}
public function testPassesTableHeaderWithAriaLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new TableHeaderNotEmpty());
$violations = $scanner->scan('<table><tr><th aria-label="Action buttons"></th><td>Edit</td></tr></table>');
$this->assertCount(0, $violations);
}
}
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\TableHeadersAttributeValid;
class TableHeadersAttributeValidTest extends TestCase
{
public function testFlagsInvalidHeaderId(): void
{
$scanner = new Scanner();
$scanner->addRule(new TableHeadersAttributeValid());
$violations = $scanner->scan('<table><tr><th id="h1">Header 1</th></tr><tr><td headers="nonexistent_h">Cell</td></tr></table>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_TABLE_HEADERS_ATTR', $violations[0]->ruleId);
}
public function testPassesValidHeaderId(): void
{
$scanner = new Scanner();
$scanner->addRule(new TableHeadersAttributeValid());
$violations = $scanner->scan('<table><tr><th id="h1">Header 1</th></tr><tr><td headers="h1">Cell</td></tr></table>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\TargetSizeMinimum;
class TargetSizeMinimumTest extends TestCase
{
public function testFlagsTooSmallTarget(): void
{
$scanner = new Scanner();
$scanner->addRule(new TargetSizeMinimum());
$violations = $scanner->scan('<div><button style="width: 16px; height: 16px;">X</button></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_2_5_8_TARGET_SIZE', $violations[0]->ruleId);
}
public function testPassesAdequateTargetSize(): void
{
$scanner = new Scanner();
$scanner->addRule(new TargetSizeMinimum());
$violations = $scanner->scan('<div><button style="width: 24px; height: 24px;">X</button></div>');
$this->assertCount(0, $violations);
}
public function testPassesElementsWithoutInlineSize(): void
{
$scanner = new Scanner();
$scanner->addRule(new TargetSizeMinimum());
$violations = $scanner->scan('<div><a href="/link">Normal Link</a></div>');
$this->assertCount(0, $violations);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace YakNet\AccessibilityConsole\Tests\Rules;
use PHPUnit\Framework\TestCase;
use YakNet\AccessibilityConsole\Core\Scanner;
use YakNet\AccessibilityConsole\Rules\TrackValidation;
class TrackValidationTest extends TestCase
{
public function testFlagsInvalidTrackKind(): void
{
$scanner = new Scanner();
$scanner->addRule(new TrackValidation());
$violations = $scanner->scan('<video src="v.mp4"><track kind="invalid_kind" src="track.vtt"></video>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_2_2_TRACK_VALIDATION', $violations[0]->ruleId);
}
public function testFlagsMissingSrclangOnCaptions(): void
{
$scanner = new Scanner();
$scanner->addRule(new TrackValidation());
$violations = $scanner->scan('<video src="v.mp4"><track kind="captions" src="track.vtt" label="English"></video>');
$this->assertCount(1, $violations);
}
public function testPassesValidTrack(): void
{
$scanner = new Scanner();
$scanner->addRule(new TrackValidation());
$violations = $scanner->scan('<video src="v.mp4"><track kind="captions" src="track.vtt" srclang="en" label="English Captions"></video>');
$this->assertCount(0, $violations);
}
}