feat: add 15 new WCAG rules, dynamic ARIA checks and bump version to v3.1.4

This commit is contained in:
Enes Yakıştır
2026-07-30 20:34:41 +03:00
parent 164828f9f7
commit 58b1618d96
31 changed files with 1138 additions and 1 deletions
+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\AnchorMissingHref;
class AnchorMissingHrefTest extends TestCase
{
public function testFlagsAnchorWithoutHref(): void
{
$scanner = new Scanner();
$scanner->addRule(new AnchorMissingHref());
$violations = $scanner->scan('<div><a>Empty Link</a></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ANCHOR_HREF', $violations[0]->ruleId);
}
public function testPassesAnchorWithValidHref(): void
{
$scanner = new Scanner();
$scanner->addRule(new AnchorMissingHref());
$violations = $scanner->scan('<div><a href="https://example.com">Valid Link</a></div>');
$this->assertCount(0, $violations);
}
public function testPassesAnchorWithRole(): void
{
$scanner = new Scanner();
$scanner->addRule(new AnchorMissingHref());
$violations = $scanner->scan('<div><a role="button">Button Link</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\AppletBanned;
class AppletBannedTest extends TestCase
{
public function testFlagsAppletTag(): void
{
$scanner = new Scanner();
$scanner->addRule(new AppletBanned());
$violations = $scanner->scan('<div><applet code="MyApplet.class"></applet></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_APPLET_BANNED', $violations[0]->ruleId);
}
public function testPassesObjectTag(): void
{
$scanner = new Scanner();
$scanner->addRule(new AppletBanned());
$violations = $scanner->scan('<div><object data="movie.mp4">Fallback</object></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\AriaExpandedState;
class AriaExpandedStateTest extends TestCase
{
public function testFlagsDropdownToggleWithoutAriaExpanded(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaExpandedState());
$violations = $scanner->scan('<button class="dropdown-toggle" data-bs-toggle="dropdown">Menu</button>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_EXPANDED', $violations[0]->ruleId);
}
public function testPassesDropdownToggleWithAriaExpanded(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaExpandedState());
$violations = $scanner->scan('<button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Menu</button>');
$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\AriaHiddenBody;
class AriaHiddenBodyTest extends TestCase
{
public function testFlagsAriaHiddenBody(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaHiddenBody());
$violations = $scanner->scan('<body aria-hidden="true"><div>Content</div></body>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_ARIA_HIDDEN_BODY', $violations[0]->ruleId);
}
public function testPassesNormalBody(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaHiddenBody());
$violations = $scanner->scan('<body><div>Content</div></body>');
$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\AriaLiveStatus;
class AriaLiveStatusTest extends TestCase
{
public function testFlagsToastContainerWithoutAriaLive(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaLiveStatus());
$violations = $scanner->scan('<div class="toast">Saving changes...</div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_3_STATUS_MESSAGES', $violations[0]->ruleId);
}
public function testPassesToastWithAriaLive(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaLiveStatus());
$violations = $scanner->scan('<div class="toast" aria-live="polite">Saving changes...</div>');
$this->assertCount(0, $violations);
}
public function testPassesToastWithRoleStatus(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaLiveStatus());
$violations = $scanner->scan('<div class="toast" role="status">Saving changes...</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\AriaPressedToggle;
class AriaPressedToggleTest extends TestCase
{
public function testFlagsToggleButtonWithoutAriaPressed(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaPressedToggle());
$violations = $scanner->scan('<button class="btn-toggle">Mute</button>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_TOGGLE_BUTTON_STATE', $violations[0]->ruleId);
}
public function testPassesToggleButtonWithAriaPressed(): void
{
$scanner = new Scanner();
$scanner->addRule(new AriaPressedToggle());
$violations = $scanner->scan('<button class="btn-toggle" aria-pressed="false">Mute</button>');
$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\ButtonLoadingState;
class ButtonLoadingStateTest extends TestCase
{
public function testFlagsLoadingButtonWithoutAriaBusy(): void
{
$scanner = new Scanner();
$scanner->addRule(new ButtonLoadingState());
$violations = $scanner->scan('<button class="btn is-loading"><span class="spinner"></span> Submit</button>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_BUTTON_LOADING_STATE', $violations[0]->ruleId);
}
public function testPassesLoadingButtonWithAriaBusy(): void
{
$scanner = new Scanner();
$scanner->addRule(new ButtonLoadingState());
$violations = $scanner->scan('<button class="btn is-loading" aria-busy="true"><span class="spinner"></span> Submit</button>');
$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\FigureCaptionStructure;
class FigureCaptionStructureTest extends TestCase
{
public function testFlagsOrphanFigcaption(): void
{
$scanner = new Scanner();
$scanner->addRule(new FigureCaptionStructure());
$violations = $scanner->scan('<div><figcaption>Caption text</figcaption></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_FIGCAPTION_PARENT', $violations[0]->ruleId);
}
public function testPassesValidFigcaptionInFigure(): void
{
$scanner = new Scanner();
$scanner->addRule(new FigureCaptionStructure());
$violations = $scanner->scan('<figure><img src="img.jpg" alt="test"><figcaption>Caption text</figcaption></figure>');
$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\FocusableWithoutRole;
class FocusableWithoutRoleTest extends TestCase
{
public function testFlagsFocusableDivWithoutRole(): void
{
$scanner = new Scanner();
$scanner->addRule(new FocusableWithoutRole());
$violations = $scanner->scan('<div tabindex="0">Click me</div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_4_1_2_FOCUSABLE_ROLE', $violations[0]->ruleId);
}
public function testPassesFocusableDivWithRole(): void
{
$scanner = new Scanner();
$scanner->addRule(new FocusableWithoutRole());
$violations = $scanner->scan('<div tabindex="0" role="button">Click me</div>');
$this->assertCount(0, $violations);
}
public function testPassesNegativeTabindexDiv(): void
{
$scanner = new Scanner();
$scanner->addRule(new FocusableWithoutRole());
$violations = $scanner->scan('<div tabindex="-1">Programmatic focus only</div>');
$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\InputMissingAutocomplete;
class InputMissingAutocompleteTest extends TestCase
{
public function testFlagsPersonalDataInputWithoutAutocomplete(): void
{
$scanner = new Scanner();
$scanner->addRule(new InputMissingAutocomplete());
$violations = $scanner->scan('<form><input type="email" name="user_email"></form>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_5_INPUT_AUTOCOMPLETE', $violations[0]->ruleId);
}
public function testPassesPersonalDataInputWithAutocomplete(): void
{
$scanner = new Scanner();
$scanner->addRule(new InputMissingAutocomplete());
$violations = $scanner->scan('<form><input type="email" name="user_email" autocomplete="email"></form>');
$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\ListItemParent;
class ListItemParentTest extends TestCase
{
public function testFlagsOrphanLiElement(): void
{
$scanner = new Scanner();
$scanner->addRule(new ListItemParent());
$violations = $scanner->scan('<div><li>Orphan Item</li></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_LIST_ITEM_PARENT', $violations[0]->ruleId);
}
public function testPassesLiInsideUl(): void
{
$scanner = new Scanner();
$scanner->addRule(new ListItemParent());
$violations = $scanner->scan('<ul><li>Valid Item</li></ul>');
$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\OptgroupLabel;
class OptgroupLabelTest extends TestCase
{
public function testFlagsOptgroupWithoutLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new OptgroupLabel());
$violations = $scanner->scan('<select><optgroup><option>Option 1</option></optgroup></select>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_OPTGROUP_LABEL', $violations[0]->ruleId);
}
public function testPassesOptgroupWithLabel(): void
{
$scanner = new Scanner();
$scanner->addRule(new OptgroupLabel());
$violations = $scanner->scan('<select><optgroup label="Fruits"><option>Apple</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\RadioGroupFieldset;
class RadioGroupFieldsetTest extends TestCase
{
public function testFlagsRadioGroupWithoutFieldsetLegend(): void
{
$scanner = new Scanner();
$scanner->addRule(new RadioGroupFieldset());
$violations = $scanner->scan('<div><input type="radio" name="gender" value="m"><input type="radio" name="gender" value="f"></div>');
$this->assertGreaterThanOrEqual(1, count($violations));
$this->assertSame('WCAG_1_3_1_RADIO_GROUP_FIELDSET', $violations[0]->ruleId);
}
public function testPassesRadioGroupInFieldsetWithLegend(): void
{
$scanner = new Scanner();
$scanner->addRule(new RadioGroupFieldset());
$violations = $scanner->scan('<fieldset><legend>Gender</legend><input type="radio" name="gender" value="m"><input type="radio" name="gender" value="f"></fieldset>');
$this->assertCount(0, $violations);
}
public function testPassesRadioGroupWithRoleRadiogroup(): void
{
$scanner = new Scanner();
$scanner->addRule(new RadioGroupFieldset());
$violations = $scanner->scan('<div role="radiogroup" aria-label="Gender"><input type="radio" name="gender" value="m"><input type="radio" name="gender" value="f"></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\SummaryParent;
class SummaryParentTest extends TestCase
{
public function testFlagsOrphanSummary(): void
{
$scanner = new Scanner();
$scanner->addRule(new SummaryParent());
$violations = $scanner->scan('<div><summary>Summary text</summary></div>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_SUMMARY_PARENT', $violations[0]->ruleId);
}
public function testPassesSummaryInDetails(): void
{
$scanner = new Scanner();
$scanner->addRule(new SummaryParent());
$violations = $scanner->scan('<details><summary>Summary text</summary><p>More details</p></details>');
$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\TableMultiCaption;
class TableMultiCaptionTest extends TestCase
{
public function testFlagsMultipleCaptionsInTable(): void
{
$scanner = new Scanner();
$scanner->addRule(new TableMultiCaption());
$violations = $scanner->scan('<table><caption>Caption 1</caption><caption>Caption 2</caption><tr><td>Data</td></tr></table>');
$this->assertCount(1, $violations);
$this->assertSame('WCAG_1_3_1_TABLE_MULTI_CAPTION', $violations[0]->ruleId);
}
public function testPassesSingleCaptionTable(): void
{
$scanner = new Scanner();
$scanner->addRule(new TableMultiCaption());
$violations = $scanner->scan('<table><caption>Single Caption</caption><tr><td>Data</td></tr></table>');
$this->assertCount(0, $violations);
}
}