2026-05-09 10:38:19 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Tests\Unit\Markdown;
|
|
|
|
|
|
|
|
|
|
use App\Markdown\MediaUrlResolver;
|
|
|
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
|
|
class MediaUrlResolverTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
private MediaUrlResolver $resolver;
|
|
|
|
|
|
|
|
|
|
protected function setUp(): void
|
|
|
|
|
{
|
|
|
|
|
parent::setUp();
|
|
|
|
|
$this->resolver = new MediaUrlResolver();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[DataProvider('nonMediaUrls')]
|
|
|
|
|
public function test_returns_null_for_non_media_urls(string $url): void
|
|
|
|
|
{
|
|
|
|
|
$this->assertNull($this->resolver->resolve($url));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function nonMediaUrls(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'normal image' => ['/photo.jpg'],
|
|
|
|
|
'svg' => ['/icon.svg'],
|
|
|
|
|
'png' => ['/avatar.png'],
|
|
|
|
|
'no extension' => ['/foo'],
|
|
|
|
|
'empty string' => [''],
|
|
|
|
|
'javascript scheme' => ['javascript:alert(1)'],
|
|
|
|
|
'host-only' => ['http://'],
|
|
|
|
|
'youtu.be lookalike host' => ['https://example.com/youtu.be-fake/abc'],
|
|
|
|
|
];
|
|
|
|
|
}
|
2026-05-09 10:41:29 +09:00
|
|
|
|
|
|
|
|
#[DataProvider('videoUrls')]
|
|
|
|
|
public function test_video_urls_produce_video_tag(string $url): void
|
|
|
|
|
{
|
|
|
|
|
$html = $this->resolver->resolve($url);
|
|
|
|
|
$this->assertNotNull($html);
|
|
|
|
|
$this->assertStringStartsWith('<video', $html);
|
|
|
|
|
$this->assertStringContainsString('controls', $html);
|
|
|
|
|
$this->assertStringContainsString('class="kb-video"', $html);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function videoUrls(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'mp4' => ['/demo.mp4'],
|
|
|
|
|
'webm' => ['/demo.webm'],
|
|
|
|
|
'ogv' => ['/demo.ogv'],
|
|
|
|
|
'mov' => ['/demo.mov'],
|
|
|
|
|
'm4v' => ['/demo.m4v'],
|
|
|
|
|
'uppercase extension' => ['/demo.MP4'],
|
|
|
|
|
'with query string' => ['https://example.com/path/demo.mp4?token=abc'],
|
|
|
|
|
'absolute http' => ['https://example.com/demo.mp4'],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_video_url_is_html_escaped(): void
|
|
|
|
|
{
|
|
|
|
|
$html = $this->resolver->resolve('/path/with"quote.mp4');
|
|
|
|
|
$this->assertNotNull($html);
|
|
|
|
|
$this->assertStringNotContainsString('"quote.mp4"', $html);
|
|
|
|
|
$this->assertStringContainsString('"', $html);
|
|
|
|
|
}
|
2026-05-09 10:44:35 +09:00
|
|
|
|
|
|
|
|
#[DataProvider('audioUrls')]
|
|
|
|
|
public function test_audio_urls_produce_audio_tag(string $url): void
|
|
|
|
|
{
|
|
|
|
|
$html = $this->resolver->resolve($url);
|
|
|
|
|
$this->assertNotNull($html);
|
|
|
|
|
$this->assertStringStartsWith('<audio', $html);
|
|
|
|
|
$this->assertStringContainsString('controls', $html);
|
|
|
|
|
$this->assertStringContainsString('class="kb-audio"', $html);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function audioUrls(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'mp3' => ['/clip.mp3'],
|
|
|
|
|
'wav' => ['/clip.wav'],
|
|
|
|
|
'ogg' => ['/clip.ogg'],
|
|
|
|
|
'm4a' => ['/clip.m4a'],
|
|
|
|
|
];
|
|
|
|
|
}
|
2026-05-09 10:38:19 +09:00
|
|
|
}
|