diff --git a/tests/phpunit/tests/content-parser/class-test-markpub.php b/tests/phpunit/tests/content-parser/class-test-markpub.php index 2fa50e5..bf59ea3 100644 --- a/tests/phpunit/tests/content-parser/class-test-markpub.php +++ b/tests/phpunit/tests/content-parser/class-test-markpub.php @@ -82,7 +82,7 @@ public function test_converts_headings() { $content = '

My Heading

'; $result = $this->parser->parse( $content, $post ); - $this->assertStringContainsString( '## My Heading', $result['text']['markdown'] ); + $this->assertSame( '## My Heading', $result['text']['markdown'] ); } /** @@ -93,7 +93,7 @@ public function test_converts_heading_level_3() { $content = '

Sub Heading

'; $result = $this->parser->parse( $content, $post ); - $this->assertStringContainsString( '### Sub Heading', $result['text']['markdown'] ); + $this->assertSame( '### Sub Heading', $result['text']['markdown'] ); } /** @@ -147,10 +147,8 @@ public function test_converts_code_blocks() { $post = self::factory()->post->create_and_get(); $content = '
echo "hello";
'; $result = $this->parser->parse( $content, $post ); - $md = $result['text']['markdown']; - $this->assertStringContainsString( '```', $md ); - $this->assertStringContainsString( 'echo "hello";', $md ); + $this->assertSame( "```\necho \"hello\";\n```", $result['text']['markdown'] ); } /** @@ -174,7 +172,7 @@ public function test_converts_separator() { . '

After

'; $result = $this->parser->parse( $content, $post ); - $this->assertStringContainsString( '---', $result['text']['markdown'] ); + $this->assertSame( "Before\n\n---\n\nAfter", $result['text']['markdown'] ); } /** @@ -188,22 +186,33 @@ public function test_empty_content() { /** * Test the atmosphere_html_to_markdown filter. + * + * Verifies the filter callback receives ($markdown, $content) so + * callers can inspect the raw source alongside the conversion. */ public function test_html_to_markdown_filter() { + $received = array(); + \add_filter( 'atmosphere_html_to_markdown', - static fn() => 'custom markdown', + static function ( $markdown, $content ) use ( &$received ) { + $received = array( + 'markdown' => $markdown, + 'content' => $content, + ); + return 'custom markdown'; + }, 10, 2 ); $post = self::factory()->post->create_and_get(); - $result = $this->parser->parse( - '

Hello

', - $post - ); + $source = '

Hello

'; + $result = $this->parser->parse( $source, $post ); $this->assertSame( 'custom markdown', $result['text']['markdown'] ); + $this->assertSame( 'Hello', $received['markdown'] ); + $this->assertSame( $source, $received['content'] ); \remove_all_filters( 'atmosphere_html_to_markdown' ); } @@ -265,4 +274,318 @@ public function test_parse_returns_null_when_markdown_is_empty() { $this->assertNull( $this->parser->parse( $content, $post ) ); } + + /** + * Test ordered list produces numbered markdown. + */ + public function test_listing_ordered() { + $post = self::factory()->post->create_and_get(); + $content = "\n
    " + . '
  1. First
  2. ' + . '
  3. Second
  4. ' + . '
  5. Third
  6. ' + . "
\n"; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( "1. First\n2. Second\n3. Third", $result['text']['markdown'] ); + } + + /** + * Test unordered list produces dashed markdown. + */ + public function test_listing_unordered() { + $post = self::factory()->post->create_and_get(); + $content = "\n\n"; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( "- First\n- Second\n- Third", $result['text']['markdown'] ); + } + + /** + * Test ordered list skips empty items without gapping the counter. + */ + public function test_listing_skips_empty_items_without_gap() { + $post = self::factory()->post->create_and_get(); + $content = "\n
    " + . '
  1. First
  2. ' + . '
  3. ' + . '
  4. Third
  5. ' + . "
\n"; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( "1. First\n2. Third", $result['text']['markdown'] ); + } + + /** + * Test list items preserve inline formatting. + */ + public function test_listing_preserves_inline_formatting() { + $post = self::factory()->post->create_and_get(); + $content = "\n\n"; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( '- some **bold**', $result['text']['markdown'] ); + } + + /** + * Test quote block wraps an inner paragraph in a "> " prefix. + */ + public function test_quote_with_inner_paragraph() { + $post = self::factory()->post->create_and_get(); + $content = '
' + . '

Paragraph text

' + . '
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( '> Paragraph text', $result['text']['markdown'] ); + } + + /** + * Test quote block prefixes every inner line. + */ + public function test_quote_prefixes_every_line() { + $post = self::factory()->post->create_and_get(); + $content = '
' + . '

First

' + . '

Second

' + . '
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( "> First\n> Second", $result['text']['markdown'] ); + } + + /** + * Test quote falls back to innerHTML when no innerBlocks are present. + */ + public function test_quote_innerhtml_fallback() { + $post = self::factory()->post->create_and_get(); + $content = '
Direct quote text
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( '> Direct quote text', $result['text']['markdown'] ); + } + + /** + * Test core/group containers flatten inner block markdown. + */ + public function test_container_group() { + $post = self::factory()->post->create_and_get(); + $content = '
' + . '

Inside group

' + . '
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( 'Inside group', $result['text']['markdown'] ); + } + + /** + * Test core/columns containers flatten inner block markdown. + */ + public function test_container_columns() { + $post = self::factory()->post->create_and_get(); + $content = '
' + . '

Inside columns

' + . '
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( 'Inside columns', $result['text']['markdown'] ); + } + + /** + * Test core/column containers flatten inner block markdown. + */ + public function test_container_column() { + $post = self::factory()->post->create_and_get(); + $content = '
' + . '

Inside column

' + . '
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( 'Inside column', $result['text']['markdown'] ); + } + + /** + * Test fallback delegates to container() when innerBlocks exist. + */ + public function test_fallback_delegates_to_container_with_inner_blocks() { + $post = self::factory()->post->create_and_get(); + $content = '
' + . '

Inside unknown

' + . '
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( 'Inside unknown', $result['text']['markdown'] ); + } + + /** + * Test image() skips blocks without an tag so surrounding + * content renders with no empty separator. + * + * Uses a mixed fixture so a regression returning "" instead of null + * would produce a leading blank line and fail this exact-match + * assertion (the whole-post empty guard in parse() would otherwise + * mask the handler bug). + */ + public function test_image_without_img_tag_is_skipped_cleanly() { + $post = self::factory()->post->create_and_get(); + $content = '
' + . '
Just a caption
' + . "
\n\n" + . '

After

'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( 'After', $result['text']['markdown'] ); + } + + /** + * Test heading defaults to level 2 when attrs.level is missing. + */ + public function test_heading_defaults_to_level_2() { + $post = self::factory()->post->create_and_get(); + $content = '

Default level

'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( '## Default level', $result['text']['markdown'] ); + } + + /** + * Test whitespace-only heading block is skipped cleanly. + * + * Mixed with a non-empty sibling so a regression returning "" from + * heading() would produce a leading blank line and fail the exact + * assertion (the whole-post empty guard would otherwise hide it). + */ + public function test_heading_whitespace_is_skipped_cleanly() { + $post = self::factory()->post->create_and_get(); + $content = "

\n\n" + . '

After

'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( 'After', $result['text']['markdown'] ); + } + + /** + * Test whitespace-only paragraph block is skipped cleanly. + * + * Mixed with a non-empty sibling so a regression returning "" from + * paragraph() would produce a leading blank line and fail the exact + * assertion (the whole-post empty guard would otherwise hide it). + */ + public function test_paragraph_whitespace_is_skipped_cleanly() { + $post = self::factory()->post->create_and_get(); + $content = "

\n\n" + . '

After

'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( 'After', $result['text']['markdown'] ); + } + + /** + * Test code block emits the configured language in the fence. + */ + public function test_code_emits_language_fence() { + $post = self::factory()->post->create_and_get(); + $content = '
echo 1;
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertStringStartsWith( "```php\n", $result['text']['markdown'] ); + } + + /** + * Test code block decodes HTML entities inside the fence. + */ + public function test_code_decodes_html_entities() { + $post = self::factory()->post->create_and_get(); + $content = '
<div>
'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( "```\n
\n```", $result['text']['markdown'] ); + } + + /** + * Test link URLs have parentheses percent-encoded to protect markdown syntax. + */ + public function test_link_url_parens_percent_encoded() { + $post = self::factory()->post->create_and_get(); + $content = '

See Foo.

'; + + $result = $this->parser->parse( $content, $post ); + $md = $result['text']['markdown']; + + $this->assertStringContainsString( '%28bar%29', $md ); + $this->assertStringNotContainsString( '(bar)', $md ); + } + + /** + * Test
converts to a markdown hard break (two spaces + newline). + */ + public function test_br_converts_to_hard_break() { + $post = self::factory()->post->create_and_get(); + $content = '

line1
line2

'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertStringContainsString( "line1 \nline2", $result['text']['markdown'] ); + } + + /** + * Test HTML entities are decoded in inline paragraph text. + */ + public function test_inline_html_entities_decoded() { + $post = self::factory()->post->create_and_get(); + $content = '

AT&T’s

'; + + $result = $this->parser->parse( $content, $post ); + $md = $result['text']['markdown']; + + $this->assertStringContainsString( 'AT&T', $md ); + $this->assertStringContainsString( "\xE2\x80\x99", $md ); + } + + /** + * Test inline inside a paragraph converts via inline_html_to_markdown. + */ + public function test_inline_image_inside_paragraph() { + $post = self::factory()->post->create_and_get(); + $content = '

Look x here

'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( 'Look ![x](x.jpg) here', $result['text']['markdown'] ); + } + + /** + * Test nested inline formatting (bold wrapping italic). + */ + public function test_nested_inline_formatting() { + $post = self::factory()->post->create_and_get(); + $content = '

bold italic

'; + + $result = $this->parser->parse( $content, $post ); + + $this->assertSame( '**bold *italic***', $result['text']['markdown'] ); + } }