Skip to content

Commit 39b1843

Browse files
committed
feat: add TableHeaderRule and InputTypeRule with tests (WCAG 1.3.1, 1.3.5)
1 parent c48fc8b commit 39b1843

8 files changed

Lines changed: 182 additions & 0 deletions

File tree

src/Rules/Forms/InputTypeRule.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TwigA11y\Rules\Forms;
6+
7+
use TwigA11y\Rules\AbstractA11yRule;
8+
use TwigCsFixer\Token\Tokens;
9+
10+
final class InputTypeRule extends AbstractA11yRule
11+
{
12+
/**
13+
* Check inputs with certain types have an autocomplete attribute.
14+
*/
15+
protected function process(int $tokenIndex, Tokens $tokens): void
16+
{
17+
if (0 !== $tokenIndex) {
18+
return;
19+
}
20+
21+
$full = '';
22+
foreach ($tokens->toArray() as $t) {
23+
$full .= $t->getValue();
24+
}
25+
26+
// quick bail
27+
if (!str_contains($full, '<input')) {
28+
return;
29+
}
30+
31+
// find inputs of type email/tel/name etc - we'll check only email for now
32+
if (!preg_match_all('/<input\b([^>]*\btype\s*=\s*(?:"|\')email(?:"|\')[^>]*)>/i', $full, $m, PREG_SET_ORDER)) {
33+
return;
34+
}
35+
36+
foreach ($m as $set) {
37+
$attrs = $set[1];
38+
if (!preg_match('/\bautocomplete\b\s*=\s*(?:"|\')/i', $attrs)) {
39+
$token = $tokens->get(0);
40+
$this->addError('Input of type "email" should include an autocomplete attribute.', $token, 'InputType.MissingAutocomplete');
41+
return;
42+
}
43+
}
44+
}
45+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TwigA11y\Rules\Structure;
6+
7+
use TwigA11y\Rules\AbstractA11yRule;
8+
use TwigCsFixer\Token\Tokens;
9+
10+
final class TableHeaderRule extends AbstractA11yRule
11+
{
12+
protected function process(int $tokenIndex, Tokens $tokens): void
13+
{
14+
if (0 !== $tokenIndex) {
15+
return;
16+
}
17+
18+
$full = '';
19+
foreach ($tokens->toArray() as $t) {
20+
$full .= $t->getValue();
21+
}
22+
23+
if (!str_contains($full, '<table')) {
24+
return;
25+
}
26+
27+
// Find th elements
28+
if (!preg_match_all('/<th\b([^>]*)>/i', $full, $m, PREG_SET_ORDER)) {
29+
return;
30+
}
31+
32+
foreach ($m as $set) {
33+
$attrs = $set[1];
34+
if (!preg_match('/\bscope\b\s*=\s*(?:"|\')/i', $attrs)) {
35+
$token = $tokens->get(0);
36+
$this->addError('Table header <th> elements should include a scope attribute.', $token, 'TableHeader.MissingScope');
37+
return;
38+
}
39+
}
40+
}
41+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<form>
2+
<input type="email" name="email">
3+
</form>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<form>
2+
<input type="email" name="email" autocomplete="email">
3+
</form>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TwigA11y\Tests\Rules\Forms;
6+
7+
use PHPUnit\Framework\Attributes\CoversNothing;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use TwigA11y\Rules\Forms\InputTypeRule;
10+
use TwigCsFixer\Test\AbstractRuleTestCase;
11+
12+
#[CoversNothing]
13+
/** @internal */
14+
final class InputTypeRuleTest extends AbstractRuleTestCase
15+
{
16+
#[DataProvider('provideFixtures')]
17+
public function testRule(string $fixture, array $expectedErrors): void
18+
{
19+
$this->checkRule(new InputTypeRule(), $expectedErrors, $fixture);
20+
}
21+
22+
public static function provideFixtures(): iterable
23+
{
24+
yield 'with autocomplete' => [__DIR__.'/Fixtures/valid/input_with_autocomplete.html.twig', []];
25+
26+
yield 'missing autocomplete' => [
27+
__DIR__.'/Fixtures/invalid/input_missing_autocomplete.html.twig',
28+
['InputType.InputType.MissingAutocomplete:1:1' => 'Input of type "email" should include an autocomplete attribute.'],
29+
];
30+
}
31+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<table>
2+
<thead>
3+
<tr>
4+
<th>Name</th>
5+
<th>Age</th>
6+
</tr>
7+
</thead>
8+
<tbody>
9+
<tr>
10+
<td>Alice</td>
11+
<td>30</td>
12+
</tr>
13+
</tbody>
14+
</table>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<table>
2+
<thead>
3+
<tr>
4+
<th scope="col">Name</th>
5+
<th scope="col">Age</th>
6+
</tr>
7+
</thead>
8+
<tbody>
9+
<tr>
10+
<td>Alice</td>
11+
<td>30</td>
12+
</tr>
13+
</tbody>
14+
</table>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TwigA11y\Tests\Rules\Structure;
6+
7+
use PHPUnit\Framework\Attributes\CoversNothing;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use TwigA11y\Rules\Structure\TableHeaderRule;
10+
use TwigCsFixer\Test\AbstractRuleTestCase;
11+
12+
#[CoversNothing]
13+
/** @internal */
14+
final class TableHeaderRuleTest extends AbstractRuleTestCase
15+
{
16+
#[DataProvider('provideFixtures')]
17+
public function testRule(string $fixture, array $expectedErrors): void
18+
{
19+
$this->checkRule(new TableHeaderRule(), $expectedErrors, $fixture);
20+
}
21+
22+
public static function provideFixtures(): iterable
23+
{
24+
yield 'valid table' => [__DIR__.'/Fixtures/valid/table_with_th_scope.html.twig', []];
25+
26+
yield 'missing th scope' => [
27+
__DIR__.'/Fixtures/invalid/table_missing_th_scope.html.twig',
28+
['TableHeader.TableHeader.MissingScope:1:1' => 'Table header <th> elements should include a scope attribute.'],
29+
];
30+
}
31+
}

0 commit comments

Comments
 (0)