diff --git a/fixtures/fragments/file.html b/fixtures/fragments/file.html index 5ff181f737..8e0f62d5d8 100644 --- a/fixtures/fragments/file.html +++ b/fixtures/fragments/file.html @@ -22,6 +22,8 @@ back to Upper-ÄÖö
back to öüä encoded
doesn't exist
+ To the top
+ To the top alt
diff --git a/fixtures/fragments/file1.md b/fixtures/fragments/file1.md index 69af62f4f6..4e315ec804 100644 --- a/fixtures/fragments/file1.md +++ b/fixtures/fragments/file1.md @@ -54,4 +54,12 @@ Therefore we put the test into a code block for now to prevent false positives. [Link to umlauts wrong case](#fünf-sÜße-Äpfel) [Link to umlauts with percent encoding](#f%C3%BCnf-s%C3%BC%C3%9Fe-%C3%A4pfel) +# To top fragments + +The empty "#" and "#top" fragments are always valid +without related HTML element. Browser will scroll to the top of the page. + +[Link to top of file2](file2.md#) +[Alternative link to top of file2](file2.md#top) + ##### Lets wear a hat: être diff --git a/lychee-bin/tests/cli.rs b/lychee-bin/tests/cli.rs index 5479d37ae8..55fc6dfa8e 100644 --- a/lychee-bin/tests/cli.rs +++ b/lychee-bin/tests/cli.rs @@ -1834,8 +1834,10 @@ mod cli { .stderr(contains( "fixtures/fragments/file1.md#kebab-case-fragment-1", )) - .stdout(contains("21 Total")) - .stdout(contains("17 OK")) + .stderr(contains("fixtures/fragments/file.html#top")) + .stderr(contains("fixtures/fragments/file2.md#top")) + .stdout(contains("25 Total")) + .stdout(contains("21 OK")) // 4 failures because of missing fragments .stdout(contains("4 Errors")); } diff --git a/lychee-lib/src/utils/fragment_checker.rs b/lychee-lib/src/utils/fragment_checker.rs index 1cc6c772c8..3e7099baa4 100644 --- a/lychee-lib/src/utils/fragment_checker.rs +++ b/lychee-lib/src/utils/fragment_checker.rs @@ -59,19 +59,17 @@ impl FragmentChecker { if file_type == FileType::Markdown { fragment_decoded = fragment_decoded.to_lowercase().into(); } + // Empty # and #top fragments are always valid, triggering the browser to scroll to top. + let is_emtpty_or_top_fragment = fragment.is_empty() || fragment.eq_ignore_ascii_case("top"); match self.cache.lock().await.entry(url_without_frag) { Entry::Vacant(entry) => { let content = fs::read_to_string(path).await?; let file_frags = extractor(&content); - let contains_fragment = - file_frags.contains(fragment) || file_frags.contains(&fragment_decoded as &str); entry.insert(file_frags); - Ok(contains_fragment) - } - Entry::Occupied(entry) => { - Ok(entry.get().contains(fragment) - || entry.get().contains(&fragment_decoded as &str)) } + Ok(is_emtpty_or_top_fragment + || entry.get().contains(fragment) + || entry.get().contains(&fragment_decoded as &str)) } }