true, 'verboseOutput' => true ]); function mkfile(string $body): void { $post = fopen(ROOT."/blog/dietrannynigger.md", "w"); fwrite($post, "title: 【】\n"); fwrite($post, "uuid: ".uuid()."\n"); fwrite($post, "author: 諏訪子\n"); fwrite($post, "date: ".date("Y-m-d H:i:s")."\n"); fwrite($post, "thumbnail: \n"); fwrite($post, "thumborient: center\n"); fwrite($post, "category: \n"); fwrite($post, "----\n"); fwrite($post, $body); fclose($post); } function rmfile(): void { if (is_file(ROOT."/blog/dietrannynigger.md")) unlink(ROOT."/blog/dietrannynigger.md"); } class Constants { public const START = "

"; public const END = "

"; } $test->describe('マークダウン', function($test): void { // サンプルの投稿が無いと、Markdownは404にリダイレクトしてexitするので、テスト全体が止まる。 // 一時的な投稿もblog/に書くので、サイト(本番)では動かさない if (!is_file(ROOT.'/blog/feature-test.md')) { $test->skipAll('サンプルの投稿(blog/feature-test.md)が無い'); } $test->it('メタデータをパーシング出来るはず', function($test): void { $md = new Markdown('feature-test', '/blog/'); $test->assertNotNull($md); $meta = $md->getMetadata(); $test->assertNotNull($meta); $expect = new \stdClass; $expect->title = '【マークダウン】関数'; $expect->uuid = 'a8c04518-4181-4ec6-9ef0-3f88f23b84b6'; $expect->author = '諏訪子'; $expect->date = '2025-11-07 20:33:42'; $expect->thumbnail = 'o_53803618dc1691.28179609-orig.jpg'; $expect->thumborient = 'center'; $expect->category = ['test', 'おちんちん', 'でっかいおっぱい']; $expect->css = ['code', 'blockquote', 'table', 'blink']; $test->assertEquals($expect, $meta); }); $test->it('横線はHTMLに交換出来るはず', function($test): void { mkfile('----'); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = "
"; $actual = str_replace("\n", '', trim($body)); $test->assertEquals($expect, $actual); }); $test->it('ヘッダーはHTMLに交換出来るはず', function($test): void { mkfile('# ケロケロ'); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = "

ケロケロ

"; $actual = trim($body); $test->assertEquals($expect, $actual); // --------------- mkfile('## ケロケロ'); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = "

ケロケロ

"; $actual = trim($body); $test->assertEquals($expect, $actual); // --------------- mkfile('### ケロケロ'); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = "

ケロケロ

"; $actual = trim($body); $test->assertEquals($expect, $actual); // --------------- mkfile('#### ケロケロ'); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = "

ケロケロ

"; $actual = trim($body); $test->assertEquals($expect, $actual); // --------------- mkfile('##### ケロケロ'); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = "
ケロケロ
"; $actual = trim($body); $test->assertEquals($expect, $actual); // --------------- mkfile('###### ケロケロ'); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = "
ケロケロ
"; $actual = trim($body); $test->assertEquals($expect, $actual); }); $test->it('「//」で始まる行は無視されるはず', function($test): void { mkfile("前の行\n// 秘密のメモ\n // 字下げしたメモ\n後の行"); $body = (new Markdown('dietrannynigger', '/blog/'))->parse(); $test->assertFalse(str_contains($body, '秘密のメモ')); $test->assertFalse(str_contains($body, '字下げしたメモ')); // 無い物として扱うので、段落は分かれない $test->assertEquals(1, substr_count($body, '

')); $test->assertStringContains('前の行後の行', $body); }); $test->it('「/*」から「*/」までは無視されるはず', function($test): void { mkfile("# 見出し\n/* 1行目\n\n# コメントの中の見出し\n最後の行 */\n本文\n/* 1行だけ */\n/* 後ろに文字 */ 残る\n\n- リスト"); $body = (new Markdown('dietrannynigger', '/blog/'))->parse(); $test->assertFalse(str_contains($body, '1行目')); $test->assertFalse(str_contains($body, 'コメントの中の見出し')); $test->assertFalse(str_contains($body, '最後の行')); $test->assertFalse(str_contains($body, '1行だけ')); $test->assertFalse(str_contains($body, '後ろに文字')); $test->assertStringContains('本文', $body); $test->assertStringContains('残る', $body); $test->assertStringContains('

  • リスト
  • ', $body); $test->assertEquals(1, substr_count($body, 'it('閉じていない「/*」は最後までコメントのはず', function($test): void { mkfile("見える\n/* ここから\n全部\n```c\nint x;\n```"); $body = (new Markdown('dietrannynigger', '/blog/'))->parse(); $test->assertStringContains('見える', $body); $test->assertFalse(str_contains($body, '全部')); $test->assertFalse(str_contains($body, '
    '));
      });
    
      $test->it('コードブロックの中のコメントは表示されるはず', function($test): void {
        mkfile("```c\n// Cのコメント\n/* ブロック */\nint x;\n```\n\n```txt\n/* 開いたまま\n```\n\n後の段落");
    
        $body = (new Markdown('dietrannynigger', '/blog/'))->parse();
    
        $test->assertStringContains('// Cのコメント', $body);
        $test->assertStringContains('/* ブロック */', $body);
        $test->assertStringContains('/* 開いたまま', $body);
        // コードブロックの中の「/*」はコメントを始めない
        $test->assertStringContains('後の段落', $body);
      });
    
      $test->it('コメントはプレビュー用にも取り除けるはず', function($test): void {
        $test->assertEquals("a\nb\n```\n// c\n```", Markdown::stripComments("a\r\n// x\r\n/* y\r\nz */\r\nb\r\n```\r\n// c\r\n```"));
      });
    
      $test->it('URLの「//」や行の途中の「/*」はコメントではないはず', function($test): void {
        mkfile("https://076.moe/ の // と /* は行の途中\n次の行");
    
        $body = (new Markdown('dietrannynigger', '/blog/'))->parse();
    
        $test->assertStringContains('https://076.moe/ の // と /* は行の途中', $body);
        $test->assertStringContains('次の行', $body);
      });
    
      $test->it('画像、音楽、動画はHTMLに交換出来るはず', function($test): void {
        mkfile("![](https://ass.technicalsuwako.moe/banner.png)\n
    ![ケロケロ](https://ass.technicalsuwako.moe/banner.png)\n
    ![ケロケロ#max-width: 200px;](https://ass.technicalsuwako.moe/banner.png)\n
    $[audio/ogg](https://ass.technicalsuwako.moe/砕月着信音.ogg)\n
    #[video/mp4](https://ass.technicalsuwako.moe/フリーズ.mp4)");
    
        $md = new Markdown('dietrannynigger', '/blog/');
        $test->assertNotNull($md);
    
        $body = $md->parse();
        $test->assertNotNull($body);
    
        $expect = '

    ' . '

    ケロケロ

    ' . '

    ケロケロ

    ' . '

    ' . '

    '; $actual = str_replace("\n", '', str_replace(' ', '', trim($body))); $test->assertEquals($expect, $actual); }); $test->it('基本的なマークダウンはHTMLに交換出来るはず', function($test): void { mkfile("**太文字**\n *イタリック*\n _下線_\n ~取消線~\n !:(5)明滅文字:!\n ^(72)フォントサイズ^\n %(ff00ff)フォントカラー%"); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = '

    太文字

    ' . '

    イタリック

    ' . '

    下線

    ' . '

    取消線

    ' . '

    明滅文字

    ' . '

    フォントサイズ

    ' . '

    フォントカラー

    '; $actual = str_replace("\n", '', trim($body)); $test->assertEquals($expect, $actual); }); $test->it('複数マークダウンはHTMLに交換出来るはず', function($test): void { mkfile("[^(500)(すわこ)](https://techsuwako.jp/)^"); $md = new Markdown('dietrannynigger', '/blog/'); $test->assertNotNull($md); $body = $md->parse(); $test->assertNotNull($body); $expect = Constants::START.'God(すわこ)'.Constants::END; $actual = str_replace("\n", '', trim($body)); $test->assertEquals($expect, $actual); }); $test->it('インライン数式はMathMLに交換出来るはず', function($test): void { mkfile('長さは$$\lVert \mathbf{v} \rVert = \sqrt{x^2 + y^2}$$です。'); $md = new Markdown('dietrannynigger', '/blog/'); $body = $md->parse(); $test->assertStringContains('', $body); $test->assertStringContains('𝐯', $body); $test->assertStringContains('x2', $body); $test->assertStringContains('です。', $body); $test->assertEquals([], $md->getMathErrors()); }); $test->it('LaTeXのバックスラッシュは消されないはず', function($test): void { mkfile('$$\frac{1}{2} \cdot \theta$$の後に\*エスケープ\*'); $md = new Markdown('dietrannynigger', '/blog/'); $body = $md->parse(); $test->assertStringContains('12', $body); $test->assertStringContains('⋅θ', $body); $test->assertStringContains('の後に*エスケープ*', $body); }); $test->it('数式ブロックはブロック表示のMathMLに交換出来るはず', function($test): void { mkfile("前の段落\n\n$$\n\\mathbf{M} =\n\\begin{bmatrix} a & b \\\\\nc & d \\end{bmatrix}\n$$\n\n\$\$x^2\$\$\n\n後の段落"); $md = new Markdown('dietrannynigger', '/blog/'); $body = $md->parse(); $test->assertEquals(2, substr_count($body, '
    assertEquals(2, substr_count($body, '')); $test->assertStringContains('[', $body); $test->assertStringContains('前の段落', $body); $test->assertStringContains('後の段落', $body); $test->assertEquals([], $md->getMathErrors()); }); $test->it('未対応のLaTeXコマンドはエラーとして報告されるはず', function($test): void { mkfile('$$\kerokero{x}$$'); $md = new Markdown('dietrannynigger', '/blog/'); $body = $md->parse(); $test->assertStringContains('', $body); $test->assertEquals(['\kerokero'], $md->getMathErrors()); }); $test->it('インラインコードの中身は変換されないはず', function($test): void { mkfile('`a * b * c`と`my_var_name`と`\n`と`$$x$$`'); $md = new Markdown('dietrannynigger', '/blog/'); $body = $md->parse(); $test->assertStringContains('a * b * c', $body); $test->assertStringContains('my_var_name', $body); $test->assertStringContains('\n', $body); $test->assertStringContains('$$x$$', $body); }); $test->it('コードブロックの行末のバックスラッシュは残るはず', function($test): void { mkfile("```txt\n /\\\n / \\\n```"); $md = new Markdown('dietrannynigger', '/blog/'); $body = $md->parse(); $test->assertStringContains(" /\\\n / \\
    ", $body); }); $test->it('コードタブはラジオボタンのタブに交換出来るはず', function($test): void { mkfile(":::tabs\n```slang\nfloat3 v;\n```\n```glsl\nvec3 v;\n```\n```glsl GLSL 4.50\nvec3 w;\n```\n:::\n\n後の段落"); $md = new Markdown('dietrannynigger', '/blog/'); $body = $md->parse(); $test->assertStringContains('
    ', $body); $test->assertEquals(3, substr_count($body, 'assertEquals(1, substr_count($body, 'checked="checked"')); $test->assertStringContains('>Slang', $body); $test->assertStringContains('>GLSL', $body); $test->assertStringContains('>GLSL 4.50', $body); $test->assertStringContains('float3 v;', $body); $test->assertStringContains('vec3 v;', $body); $test->assertStringContains('後の段落', $body); }); $test->it('対応している言語のコードブロックはハイライトされるはず', function($test): void { mkfile("```c\nint x = 42; // \n```\n\n```sh\necho \"\"\n```"); $md = new Markdown('dietrannynigger', '/blog/'); $body = $md->parse(); $test->assertStringContains('int x = 42; // <b>', $body); // 未対応の言語はエスケープだけ $test->assertStringContains('echo "<b>"', $body); }); $test->it('文字列から変換出来て、メタデータの区切りは無いはず', function($test): void { $body = Markdown::fromString("# 題 \n\n前\n\n----\n\n後\n")->parse(); $test->assertStringContains('題 <b>', $body); $test->assertStringContains('前', $body); $test->assertStringContains('assertStringContains('後', $body); }); rmfile(); }); $test->printSummary();