Skip to content

Commit c48fc8b

Browse files
committed
feat: add VideoTrackRule to enforce track kind="captions" on <video> (WCAG 1.2.2) with tests
1 parent 4d411f6 commit c48fc8b

4 files changed

Lines changed: 86 additions & 0 deletions

File tree

src/Rules/Media/VideoTrackRule.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TwigA11y\Rules\Media;
6+
7+
use TwigA11y\Rules\AbstractA11yRule;
8+
use TwigCsFixer\Token\Tokens;
9+
10+
final class VideoTrackRule extends AbstractA11yRule
11+
{
12+
protected function process(int $tokenIndex, Tokens $tokens): void
13+
{
14+
// Only run once per file
15+
if (0 !== $tokenIndex) {
16+
return;
17+
}
18+
19+
$full = '';
20+
foreach ($tokens->toArray() as $t) {
21+
$full .= $t->getValue();
22+
}
23+
24+
if (!str_contains($full, '<video')) {
25+
return;
26+
}
27+
28+
// Find all <video ...>...</video> blocks
29+
if (!preg_match_all('/<video\b([^>]*)>(.*?)<\/video>/is', $full, $m, PREG_SET_ORDER)) {
30+
return;
31+
}
32+
33+
foreach ($m as $set) {
34+
$openAttrs = $set[1];
35+
$content = $set[2];
36+
37+
// If a <track kind="captions" exists inside the video block, OK
38+
if (preg_match('/<track\b[^>]*\bkind\s*=\s*(?:"|\')captions(?:"|\')/i', $content)) {
39+
continue;
40+
}
41+
42+
// No captions found — report error at token 0 for determinism
43+
$token = $tokens->get(0);
44+
$this->addError('Video should have captions (track kind="captions").', $token, 'VideoTrack.MissingCaptions');
45+
return;
46+
}
47+
}
48+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<video controls>
2+
<source src="movie.mp4" type="video/mp4">
3+
</video>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<video controls>
2+
<source src="movie.mp4" type="video/mp4">
3+
<track kind="captions" srclang="en" label="English captions" src="captions_en.vtt">
4+
</video>
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\Media;
6+
7+
use PHPUnit\Framework\Attributes\CoversNothing;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use TwigA11y\Rules\Media\VideoTrackRule;
10+
use TwigCsFixer\Test\AbstractRuleTestCase;
11+
12+
#[CoversNothing]
13+
/** @internal */
14+
final class VideoTrackRuleTest extends AbstractRuleTestCase
15+
{
16+
#[DataProvider('provideFixtures')]
17+
public function testRule(string $fixture, array $expectedErrors): void
18+
{
19+
$this->checkRule(new VideoTrackRule(), $expectedErrors, $fixture);
20+
}
21+
22+
public static function provideFixtures(): iterable
23+
{
24+
yield 'video with captions' => [__DIR__.'/Fixtures/valid/video_with_captions.html.twig', []];
25+
26+
yield 'video without captions' => [
27+
__DIR__.'/Fixtures/invalid/video_no_captions.html.twig',
28+
['VideoTrack.VideoTrack.MissingCaptions:1:1' => 'Video should have captions (track kind="captions").'],
29+
];
30+
}
31+
}

0 commit comments

Comments
 (0)