From cc532cce652a6fc6afd7dd63f0177b9dfe0c9c7e Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:31:23 +0100 Subject: [PATCH 01/23] Extract helper for making the identifier --- .../framework/src/Markdown/Processing/HeadingRenderer.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Markdown/Processing/HeadingRenderer.php b/packages/framework/src/Markdown/Processing/HeadingRenderer.php index 397936b16b5..d0fa964613b 100644 --- a/packages/framework/src/Markdown/Processing/HeadingRenderer.php +++ b/packages/framework/src/Markdown/Processing/HeadingRenderer.php @@ -71,7 +71,7 @@ public function postProcess(string $html): string protected function makeHeadingId(string $contents): string { - $identifier = $this->ensureIdentifierIsUnique(Str::slug($contents)); + $identifier = $this->ensureIdentifierIsUnique(static::makeIdentifier($contents)); $this->headingRegistry[] = $identifier; @@ -89,4 +89,9 @@ protected function ensureIdentifierIsUnique(string $slug): string return $identifier; } + + protected static function makeIdentifier(string $title): string + { + return Str::slug($title); + } } From 302168bec281196fdf466f76073a6fe0cfc50c22 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:31:44 +0100 Subject: [PATCH 02/23] Make protected helper public internal --- packages/framework/src/Markdown/Processing/HeadingRenderer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Markdown/Processing/HeadingRenderer.php b/packages/framework/src/Markdown/Processing/HeadingRenderer.php index d0fa964613b..6b2cf74e2d8 100644 --- a/packages/framework/src/Markdown/Processing/HeadingRenderer.php +++ b/packages/framework/src/Markdown/Processing/HeadingRenderer.php @@ -90,7 +90,8 @@ protected function ensureIdentifierIsUnique(string $slug): string return $identifier; } - protected static function makeIdentifier(string $title): string + /** @internal */ + public static function makeIdentifier(string $title): string { return Str::slug($title); } From e2a63d5590e5cb9e16af985e2b11f59bc6bc042d Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:32:47 +0100 Subject: [PATCH 03/23] Use the same identifier generation method everywhere --- .../src/Framework/Actions/GeneratesTableOfContents.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/framework/src/Framework/Actions/GeneratesTableOfContents.php b/packages/framework/src/Framework/Actions/GeneratesTableOfContents.php index ae27d6af08c..7f217ed8861 100644 --- a/packages/framework/src/Framework/Actions/GeneratesTableOfContents.php +++ b/packages/framework/src/Framework/Actions/GeneratesTableOfContents.php @@ -6,7 +6,7 @@ use Hyde\Facades\Config; use Hyde\Markdown\Models\Markdown; -use Illuminate\Support\Str; +use Hyde\Markdown\Processing\HeadingRenderer; /** * Generates a nested table of contents from Markdown headings. @@ -117,7 +117,7 @@ protected function createHeadingEntry(array $headingData): array return [ 'level' => $headingData['level'], 'title' => $headingData['title'], - 'slug' => Str::slug($headingData['title']), + 'slug' => HeadingRenderer::makeIdentifier($headingData['title']), ]; } From 3f48395f3fb136a5e6a91d2a84e7b53b61bf96b3 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:35:17 +0100 Subject: [PATCH 04/23] Test needs kernel in isolate --- packages/framework/tests/Unit/HeadingRendererUnitTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 63a7bdc9aae..6d76c141dde 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -25,6 +25,7 @@ class HeadingRendererUnitTest extends UnitTestCase use UsesRealBladeInUnitTests; protected static bool $needsConfig = true; + protected static bool $needsKernel = true; protected static ?array $cachedConfig = null; protected function setUp(): void From c4e9e05f2476da8eaf773c9c888a1f72dc60ded7 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:37:23 +0100 Subject: [PATCH 05/23] Add unit test With fixtures from Laravel Framework test for the `Str::slug` helper --- .../framework/tests/Unit/HeadingRendererUnitTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 6d76c141dde..79544abac99 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -234,6 +234,15 @@ public function testPostProcessHandlesNoHeadingTags() $this->assertSame('

Paragraph

', (new HeadingRenderer())->postProcess($html)); } + public function testHeadingIdentifierGeneration() + { + $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello world')); + $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello-world')); + $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello_world')); + $this->assertSame('user-at-host', HeadingRenderer::makeIdentifier('user@host')); + $this->assertSame('', HeadingRenderer::makeIdentifier('')); + } + protected function mockChildNodeRenderer(string $contents = 'Test Heading'): ChildNodeRendererInterface { $childRenderer = Mockery::mock(ChildNodeRendererInterface::class); From 978493796f3f71df7efad37c9136ddf28bc359ff Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:39:30 +0100 Subject: [PATCH 06/23] Refactor to use dataprovider attribute --- .../tests/Unit/HeadingRendererUnitTest.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 79544abac99..d02d3298aca 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -14,6 +14,7 @@ use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use Mockery; +use PHPUnit\Framework\Attributes\TestWith; /** * @covers \Hyde\Markdown\Processing\HeadingRenderer @@ -234,13 +235,14 @@ public function testPostProcessHandlesNoHeadingTags() $this->assertSame('

Paragraph

', (new HeadingRenderer())->postProcess($html)); } - public function testHeadingIdentifierGeneration() + #[TestWith(['hello world', 'hello-world'])] + #[TestWith(['hello-world', 'hello-world'])] + #[TestWith(['hello_world', 'hello-world'])] + #[TestWith(['user@host', 'user-at-host'])] + #[TestWith(['', ''])] + public function testHeadingIdentifierGeneration(string $input, string $expected): void { - $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello world')); - $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello-world')); - $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello_world')); - $this->assertSame('user-at-host', HeadingRenderer::makeIdentifier('user@host')); - $this->assertSame('', HeadingRenderer::makeIdentifier('')); + $this->assertSame($expected, HeadingRenderer::makeIdentifier($input)); } protected function mockChildNodeRenderer(string $contents = 'Test Heading'): ChildNodeRendererInterface From be846bf0a646ab4ca7a59f0acfcfd654115ea652 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:40:42 +0100 Subject: [PATCH 07/23] Revert "Refactor to use dataprovider attribute" This reverts commit 978493796f3f71df7efad37c9136ddf28bc359ff. --- .../tests/Unit/HeadingRendererUnitTest.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index d02d3298aca..79544abac99 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -14,7 +14,6 @@ use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use Mockery; -use PHPUnit\Framework\Attributes\TestWith; /** * @covers \Hyde\Markdown\Processing\HeadingRenderer @@ -235,14 +234,13 @@ public function testPostProcessHandlesNoHeadingTags() $this->assertSame('

Paragraph

', (new HeadingRenderer())->postProcess($html)); } - #[TestWith(['hello world', 'hello-world'])] - #[TestWith(['hello-world', 'hello-world'])] - #[TestWith(['hello_world', 'hello-world'])] - #[TestWith(['user@host', 'user-at-host'])] - #[TestWith(['', ''])] - public function testHeadingIdentifierGeneration(string $input, string $expected): void + public function testHeadingIdentifierGeneration() { - $this->assertSame($expected, HeadingRenderer::makeIdentifier($input)); + $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello world')); + $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello-world')); + $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello_world')); + $this->assertSame('user-at-host', HeadingRenderer::makeIdentifier('user@host')); + $this->assertSame('', HeadingRenderer::makeIdentifier('')); } protected function mockChildNodeRenderer(string $contents = 'Test Heading'): ChildNodeRendererInterface From 7b2a9c73eee76a670a8a698fcd35e588b7d65c70 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:41:31 +0100 Subject: [PATCH 08/23] Refactor to use dataprovider --- .../tests/Unit/HeadingRendererUnitTest.php | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 79544abac99..f9796f80937 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -234,13 +234,12 @@ public function testPostProcessHandlesNoHeadingTags() $this->assertSame('

Paragraph

', (new HeadingRenderer())->postProcess($html)); } - public function testHeadingIdentifierGeneration() + /** + * @dataProvider headingIdentifierProvider + */ + public function testHeadingIdentifierGeneration($input, $expected) { - $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello world')); - $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello-world')); - $this->assertSame('hello-world', HeadingRenderer::makeIdentifier('hello_world')); - $this->assertSame('user-at-host', HeadingRenderer::makeIdentifier('user@host')); - $this->assertSame('', HeadingRenderer::makeIdentifier('')); + $this->assertSame($expected, HeadingRenderer::makeIdentifier($input)); } protected function mockChildNodeRenderer(string $contents = 'Test Heading'): ChildNodeRendererInterface @@ -265,4 +264,15 @@ protected function validateHeadingPermalinkStates(HeadingRenderer $renderer, Chi } } } + + public static function headingIdentifierProvider(): array + { + return [ + ['hello world', 'hello-world'], + ['hello-world', 'hello-world'], + ['hello_world', 'hello-world'], + ['user@host', 'user-at-host'], + ['', ''], + ]; + } } From d36d8a94cd211d430e4efb2409ad2e76e03ef915 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:55:55 +0100 Subject: [PATCH 09/23] Add some fixtures for common real headings --- packages/framework/tests/Unit/HeadingRendererUnitTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index f9796f80937..21f3d4d2eac 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -273,6 +273,11 @@ public static function headingIdentifierProvider(): array ['hello_world', 'hello-world'], ['user@host', 'user-at-host'], ['', ''], + ['Introduction', 'introduction'], + ['Getting Started', 'getting-started'], + ['Installation Guide', 'installation-guide'], + ['API Reference', 'api-reference'], + ['Frequently Asked Questions', 'frequently-asked-questions'], ]; } } From 416338ee138f5bde827583491b6de4cb1f29972b Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 21:57:36 +0100 Subject: [PATCH 10/23] Add some fixtures for some edge cases --- .../framework/tests/Unit/HeadingRendererUnitTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 21f3d4d2eac..35bc14a8e26 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -278,6 +278,18 @@ public static function headingIdentifierProvider(): array ['Installation Guide', 'installation-guide'], ['API Reference', 'api-reference'], ['Frequently Asked Questions', 'frequently-asked-questions'], + ['123 heading', '123-heading'], + ['heading with multiple spaces', 'heading-with-multiple-spaces'], + ['heading_with_underscores_and-dashes', 'heading-with-underscores-and-dashes'], + ['heading with special characters !@#$', 'heading-with-special-characters-at'], + ['heading with numbers 123', 'heading-with-numbers-123'], + ['UPPERCASE HEADING', 'uppercase-heading'], + ['heading with emoji 😊', 'heading-with-emoji'], + ['heading with mixed CASE and 123 numbers', 'heading-with-mixed-case-and-123-numbers'], + ['heading with punctuation, commas, and periods.', 'heading-with-punctuation-commas-and-periods'], + ['heading with quotes "double" and \'single\'', 'heading-with-quotes-double-and-single'], + ['heading with slashes / and \\', 'heading-with-slashes-and'], + ['heading with parentheses (and brackets) [and braces] {and more}', 'heading-with-parentheses-and-brackets-and-braces-and-more'], ]; } } From 94577e6fb8c81fb7381c22eece72edf02e0330e2 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:01:17 +0100 Subject: [PATCH 11/23] Grouped and improved fixtures --- .../tests/Unit/HeadingRendererUnitTest.php | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 35bc14a8e26..c7d34e244dc 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -268,28 +268,39 @@ protected function validateHeadingPermalinkStates(HeadingRenderer $renderer, Chi public static function headingIdentifierProvider(): array { return [ - ['hello world', 'hello-world'], - ['hello-world', 'hello-world'], - ['hello_world', 'hello-world'], - ['user@host', 'user-at-host'], - ['', ''], - ['Introduction', 'introduction'], - ['Getting Started', 'getting-started'], - ['Installation Guide', 'installation-guide'], - ['API Reference', 'api-reference'], - ['Frequently Asked Questions', 'frequently-asked-questions'], - ['123 heading', '123-heading'], - ['heading with multiple spaces', 'heading-with-multiple-spaces'], - ['heading_with_underscores_and-dashes', 'heading-with-underscores-and-dashes'], - ['heading with special characters !@#$', 'heading-with-special-characters-at'], - ['heading with numbers 123', 'heading-with-numbers-123'], - ['UPPERCASE HEADING', 'uppercase-heading'], - ['heading with emoji 😊', 'heading-with-emoji'], - ['heading with mixed CASE and 123 numbers', 'heading-with-mixed-case-and-123-numbers'], - ['heading with punctuation, commas, and periods.', 'heading-with-punctuation-commas-and-periods'], - ['heading with quotes "double" and \'single\'', 'heading-with-quotes-double-and-single'], - ['heading with slashes / and \\', 'heading-with-slashes-and'], - ['heading with parentheses (and brackets) [and braces] {and more}', 'heading-with-parentheses-and-brackets-and-braces-and-more'], + // Basic cases + ['Hello World', 'hello-world'], + ['Simple Heading', 'simple-heading'], + ['Heading With Numbers 123', 'heading-with-numbers-123'], + + // Special characters + ['Heading with & symbol', 'heading-with-amp-symbol'], + ['Heading with < > symbols', 'heading-with-lt-gt-symbols'], + ['Heading with "quotes"', 'heading-with-quot-quotes'], + ['Heading with / and \\', 'heading-with-and'], + ['Heading with punctuation!?!', 'heading-with-punctuation'], + ['Hyphenated-heading-name', 'hyphenated-heading-name'], + + // Emojis + ['Heading with emoji 🎉', 'heading-with-emoji'], + ['Another emoji 🤔 test', 'another-emoji-test'], + ['Multiple emojis 🎉🤔✨', 'multiple-emojis'], + + // Accented and non-ASCII characters + ['Accented é character', 'accented-e-character'], + ['Café Crème', 'cafe-creme'], + ['Łódź and święto', 'lodz-and-swieto'], + ['中文标题', ''], + ['日本語の見出し', ''], + + // Edge cases + [' Leading spaces', 'leading-spaces'], + ['Trailing spaces ', 'trailing-spaces'], + [' Surrounded by spaces ', 'surrounded-by-spaces'], + ['----', ''], // All hyphens + ['%%%%%%%', ''], // All special characters + [' ', ''], // Empty after trimming + ['1234567890', '1234567890'], // Numbers only ]; } } From 0f2e322e39ad49feed5ab3c9d5181d26ae0ce410 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:12:22 +0100 Subject: [PATCH 12/23] Transliterate heading identifiers Adds only ~0.20ms for the entire dataprovider set, so I think this small thing is worth it --- .../framework/src/Markdown/Processing/HeadingRenderer.php | 2 +- packages/framework/tests/Unit/HeadingRendererUnitTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/framework/src/Markdown/Processing/HeadingRenderer.php b/packages/framework/src/Markdown/Processing/HeadingRenderer.php index 6b2cf74e2d8..ce7305b3a71 100644 --- a/packages/framework/src/Markdown/Processing/HeadingRenderer.php +++ b/packages/framework/src/Markdown/Processing/HeadingRenderer.php @@ -93,6 +93,6 @@ protected function ensureIdentifierIsUnique(string $slug): string /** @internal */ public static function makeIdentifier(string $title): string { - return Str::slug($title); + return Str::slug(Str::transliterate($title)); } } diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index c7d34e244dc..3cf2132afac 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -290,8 +290,8 @@ public static function headingIdentifierProvider(): array ['Accented é character', 'accented-e-character'], ['Café Crème', 'cafe-creme'], ['Łódź and święto', 'lodz-and-swieto'], - ['中文标题', ''], - ['日本語の見出し', ''], + ['中文标题', 'zhong-wen-biao-ti'], + ['日本語の見出し', 'ri-ben-yu-nojian-chu-si'], // Edge cases [' Leading spaces', 'leading-spaces'], From fb9f6523756cfccfaf9c330549780acda30743c1 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:12:32 +0100 Subject: [PATCH 13/23] Add additional fixture --- packages/framework/tests/Unit/HeadingRendererUnitTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 3cf2132afac..23655e4875b 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -292,6 +292,7 @@ public static function headingIdentifierProvider(): array ['Łódź and święto', 'lodz-and-swieto'], ['中文标题', 'zhong-wen-biao-ti'], ['日本語の見出し', 'ri-ben-yu-nojian-chu-si'], + ['한국어 제목', 'han-gug-eo-jemog'], // Edge cases [' Leading spaces', 'leading-spaces'], From 25e0a0173bd93d9de6d87dfaa5572113cc4a82d4 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:13:26 +0100 Subject: [PATCH 14/23] Inline default dictionary value --- .../framework/src/Markdown/Processing/HeadingRenderer.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Markdown/Processing/HeadingRenderer.php b/packages/framework/src/Markdown/Processing/HeadingRenderer.php index ce7305b3a71..db271ef5e8b 100644 --- a/packages/framework/src/Markdown/Processing/HeadingRenderer.php +++ b/packages/framework/src/Markdown/Processing/HeadingRenderer.php @@ -93,6 +93,8 @@ protected function ensureIdentifierIsUnique(string $slug): string /** @internal */ public static function makeIdentifier(string $title): string { - return Str::slug(Str::transliterate($title)); + return Str::slug(Str::transliterate($title), dictionary: [ + '@' => 'at', + ]); } } From 5881e138f82c81938a4197598ca7e5866a818889 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:13:55 +0100 Subject: [PATCH 15/23] Expand ampersands --- packages/framework/src/Markdown/Processing/HeadingRenderer.php | 1 + packages/framework/tests/Unit/HeadingRendererUnitTest.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Markdown/Processing/HeadingRenderer.php b/packages/framework/src/Markdown/Processing/HeadingRenderer.php index db271ef5e8b..479b3455e1a 100644 --- a/packages/framework/src/Markdown/Processing/HeadingRenderer.php +++ b/packages/framework/src/Markdown/Processing/HeadingRenderer.php @@ -95,6 +95,7 @@ public static function makeIdentifier(string $title): string { return Str::slug(Str::transliterate($title), dictionary: [ '@' => 'at', + '&' => 'and', ]); } } diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 23655e4875b..edf6fe45764 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -274,7 +274,7 @@ public static function headingIdentifierProvider(): array ['Heading With Numbers 123', 'heading-with-numbers-123'], // Special characters - ['Heading with & symbol', 'heading-with-amp-symbol'], + ['Heading with & symbol', 'heading-with-and-symbol'], ['Heading with < > symbols', 'heading-with-lt-gt-symbols'], ['Heading with "quotes"', 'heading-with-quot-quotes'], ['Heading with / and \\', 'heading-with-and'], From 316023d8a8e511315a4e10fae5ede924aef36ac2 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:15:48 +0100 Subject: [PATCH 16/23] Strip escaped angle brackets --- packages/framework/src/Markdown/Processing/HeadingRenderer.php | 2 ++ packages/framework/tests/Unit/HeadingRendererUnitTest.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/framework/src/Markdown/Processing/HeadingRenderer.php b/packages/framework/src/Markdown/Processing/HeadingRenderer.php index 479b3455e1a..125a3aa9881 100644 --- a/packages/framework/src/Markdown/Processing/HeadingRenderer.php +++ b/packages/framework/src/Markdown/Processing/HeadingRenderer.php @@ -96,6 +96,8 @@ public static function makeIdentifier(string $title): string return Str::slug(Str::transliterate($title), dictionary: [ '@' => 'at', '&' => 'and', + '<' => '', + '>' => '', ]); } } diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index edf6fe45764..94f5249a26d 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -275,7 +275,7 @@ public static function headingIdentifierProvider(): array // Special characters ['Heading with & symbol', 'heading-with-and-symbol'], - ['Heading with < > symbols', 'heading-with-lt-gt-symbols'], + ['Heading with < > symbols', 'heading-with-symbols'], ['Heading with "quotes"', 'heading-with-quot-quotes'], ['Heading with / and \\', 'heading-with-and'], ['Heading with punctuation!?!', 'heading-with-punctuation'], From fe6864ba54beaba8fe2438f2c26c558843703804 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:16:11 +0100 Subject: [PATCH 17/23] Fix typo --- packages/framework/tests/Unit/HeadingRendererUnitTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 94f5249a26d..6bd5d346a35 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -291,7 +291,7 @@ public static function headingIdentifierProvider(): array ['Café Crème', 'cafe-creme'], ['Łódź and święto', 'lodz-and-swieto'], ['中文标题', 'zhong-wen-biao-ti'], - ['日本語の見出し', 'ri-ben-yu-nojian-chu-si'], + ['日本語の見出し', 'ri-ben-yu-nojian-chu-shi'], ['한국어 제목', 'han-gug-eo-jemog'], // Edge cases From 19a63ff2d0c15602e2a3e496ed5493da386f9752 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:16:38 +0100 Subject: [PATCH 18/23] Fix faulty expectation --- packages/framework/tests/Unit/HeadingRendererUnitTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 6bd5d346a35..ce7c56ac142 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -276,7 +276,7 @@ public static function headingIdentifierProvider(): array // Special characters ['Heading with & symbol', 'heading-with-and-symbol'], ['Heading with < > symbols', 'heading-with-symbols'], - ['Heading with "quotes"', 'heading-with-quot-quotes'], + ['Heading with "quotes"', 'heading-with-quotes'], ['Heading with / and \\', 'heading-with-and'], ['Heading with punctuation!?!', 'heading-with-punctuation'], ['Hyphenated-heading-name', 'hyphenated-heading-name'], From 3b7894f7b40a140d1381f8f82bd4db2b99e973de Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:16:51 +0100 Subject: [PATCH 19/23] Fix transliterated value --- packages/framework/tests/Unit/HeadingRendererUnitTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index ce7c56ac142..07b476dd10f 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -292,7 +292,7 @@ public static function headingIdentifierProvider(): array ['Łódź and święto', 'lodz-and-swieto'], ['中文标题', 'zhong-wen-biao-ti'], ['日本語の見出し', 'ri-ben-yu-nojian-chu-shi'], - ['한국어 제목', 'han-gug-eo-jemog'], + ['한국어 제목', 'hangugeo-jemog'], // Edge cases [' Leading spaces', 'leading-spaces'], From b6bf4499b3c47925f1b73ea9ea556615f1e26713 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:21:55 +0100 Subject: [PATCH 20/23] Test heading identifier generation with escaped input --- .../framework/tests/Unit/HeadingRendererUnitTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index 07b476dd10f..f7662e1d6bf 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -242,6 +242,15 @@ public function testHeadingIdentifierGeneration($input, $expected) $this->assertSame($expected, HeadingRenderer::makeIdentifier($input)); } + /** + * @dataProvider headingIdentifierProvider + */ + public function testHeadingIdentifierGenerationWithEscapedInput($input, $expected) + { + $this->assertSame(HeadingRenderer::makeIdentifier($input), HeadingRenderer::makeIdentifier(e($input))); + $this->assertSame($expected, HeadingRenderer::makeIdentifier(e($input))); + } + protected function mockChildNodeRenderer(string $contents = 'Test Heading'): ChildNodeRendererInterface { $childRenderer = Mockery::mock(ChildNodeRendererInterface::class); From d78add71e5b284d58e4989b8ebeebced36f34cea Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:27:53 +0100 Subject: [PATCH 21/23] Ensure it works with escaped inputs Found that decoding and reescaping is the most reliable and easiest method, and it has virtually no performance impact --- .../framework/src/Markdown/Processing/HeadingRenderer.php | 4 ++-- .../framework/tests/Feature/MarkdownHeadingRendererTest.php | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/framework/src/Markdown/Processing/HeadingRenderer.php b/packages/framework/src/Markdown/Processing/HeadingRenderer.php index 125a3aa9881..8c7f072f0fd 100644 --- a/packages/framework/src/Markdown/Processing/HeadingRenderer.php +++ b/packages/framework/src/Markdown/Processing/HeadingRenderer.php @@ -93,11 +93,11 @@ protected function ensureIdentifierIsUnique(string $slug): string /** @internal */ public static function makeIdentifier(string $title): string { - return Str::slug(Str::transliterate($title), dictionary: [ + return e(Str::slug(Str::transliterate(html_entity_decode($title)), dictionary: [ '@' => 'at', '&' => 'and', '<' => '', '>' => '', - ]); + ])); } } diff --git a/packages/framework/tests/Feature/MarkdownHeadingRendererTest.php b/packages/framework/tests/Feature/MarkdownHeadingRendererTest.php index f69d07a9e2a..a254be2949d 100644 --- a/packages/framework/tests/Feature/MarkdownHeadingRendererTest.php +++ b/packages/framework/tests/Feature/MarkdownHeadingRendererTest.php @@ -183,9 +183,8 @@ public function testHeadingsWithSpecialCharacters() $this->assertStringContainsString('Heading with & special < > "characters"', $html); $this->assertStringContainsString('Heading with émojis 🎉', $html); - // Todo: Try to normalize to heading-with-special-characters? $this->assertSame(<<<'HTML' -

Heading with & special < > "characters"#

+

Heading with & special < > "characters"#

Heading with émojis 🎉#

HTML, $html); From c240c15f01c6aa736d2e0d4dee85cfba03ab434f Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:29:34 +0100 Subject: [PATCH 22/23] Join comma-separated values into a single line --- .../framework/src/Markdown/Processing/HeadingRenderer.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/framework/src/Markdown/Processing/HeadingRenderer.php b/packages/framework/src/Markdown/Processing/HeadingRenderer.php index 8c7f072f0fd..dcb4517c3f9 100644 --- a/packages/framework/src/Markdown/Processing/HeadingRenderer.php +++ b/packages/framework/src/Markdown/Processing/HeadingRenderer.php @@ -93,11 +93,6 @@ protected function ensureIdentifierIsUnique(string $slug): string /** @internal */ public static function makeIdentifier(string $title): string { - return e(Str::slug(Str::transliterate(html_entity_decode($title)), dictionary: [ - '@' => 'at', - '&' => 'and', - '<' => '', - '>' => '', - ])); + return e(Str::slug(Str::transliterate(html_entity_decode($title)), dictionary: ['@' => 'at', '&' => 'and', '<' => '', '>' => ''])); } } From 50f89f85450e26f07c8f8d102f969a82053c6bc3 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Sat, 7 Dec 2024 22:44:36 +0100 Subject: [PATCH 23/23] Cleanup code --- packages/framework/tests/Unit/HeadingRendererUnitTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/framework/tests/Unit/HeadingRendererUnitTest.php b/packages/framework/tests/Unit/HeadingRendererUnitTest.php index f7662e1d6bf..2b71abb4677 100644 --- a/packages/framework/tests/Unit/HeadingRendererUnitTest.php +++ b/packages/framework/tests/Unit/HeadingRendererUnitTest.php @@ -307,10 +307,10 @@ public static function headingIdentifierProvider(): array [' Leading spaces', 'leading-spaces'], ['Trailing spaces ', 'trailing-spaces'], [' Surrounded by spaces ', 'surrounded-by-spaces'], - ['----', ''], // All hyphens - ['%%%%%%%', ''], // All special characters - [' ', ''], // Empty after trimming - ['1234567890', '1234567890'], // Numbers only + ['----', ''], + ['%%%%%%%', ''], + [' ', ''], + ['1234567890', '1234567890'], ]; } }