fix: fixed rendering from validate-apply

This commit is contained in:
2026-07-23 21:51:12 +02:00
parent 2844ed5a47
commit ea90167a5a
6 changed files with 216 additions and 13 deletions

View File

@@ -315,6 +315,9 @@ pub fn sections_from_validation_report(
.chain(report.stale_pages.iter())
{
match classify_generated_path(path, metadata) {
Some(GenerationSection::Single) => {
sections.extend(GenerationSection::ALL);
}
Some(section) => {
sections.insert(section);
}

View File

@@ -269,6 +269,16 @@ fn build_site_render_artifacts_with_mode(
&tags,
&category_settings,
);
let expanded_requested_paths = requested_paths.map(|requested| {
expand_requested_aggregate_paths(
requested,
&localized_posts,
&routes,
&language,
metadata,
)
});
let requested_paths = expanded_requested_paths.as_ref();
artifacts
.route_manifest
.extend(routes.iter().map(|route| SitePage {
@@ -869,6 +879,86 @@ fn paginated_route_specs(
pages
}
fn expand_requested_aggregate_paths(
requested: &HashSet<String>,
posts: &[RenderPostRecord],
routes: &[RouteSpec],
language: &str,
metadata: &ProjectMetadata,
) -> HashSet<String> {
let mut expanded = requested.clone();
let root = language_root_prefix(language, metadata)
.trim_matches('/')
.to_string();
let prefixed = |suffix: String| {
if root.is_empty() {
suffix
} else {
format!("{root}/{suffix}")
}
};
for record in posts {
let post_path = format!(
"{}/index.html",
build_canonical_post_path(&record.post, language, main_language(metadata))
.trim_start_matches('/')
);
if !requested.contains(&post_path) {
continue;
}
let mut families = vec![root.clone()];
families.extend(
record
.post
.categories
.iter()
.map(|category| prefixed(format!("category/{}", slugify(category)))),
);
families.extend(
record
.post
.tags
.iter()
.map(|tag| prefixed(format!("tag/{}", slugify(tag)))),
);
if let Some(date) = Local.timestamp_millis_opt(record.post.created_at).single() {
families.extend([
prefixed(format!("{:04}", date.year())),
prefixed(format!("{:04}/{:02}", date.year(), date.month())),
prefixed(format!(
"{:04}/{:02}/{:02}",
date.year(),
date.month(),
date.day()
)),
]);
}
expanded.extend(
routes
.iter()
.filter(|route| {
families
.iter()
.any(|family| route_belongs_to_family(&route.relative_path, family))
})
.map(|route| route.relative_path.clone()),
);
}
expanded
}
fn route_belongs_to_family(path: &str, family: &str) -> bool {
if family.is_empty() {
path == "index.html" || path.starts_with("page/")
} else {
path == format!("{family}/index.html") || path.starts_with(&format!("{family}/page/"))
}
}
#[expect(
clippy::too_many_arguments,
reason = "render inputs are existing domain data with distinct lifetimes"
@@ -1276,7 +1366,7 @@ fn should_show_list_title(
category_settings
.get(category)
.map(|settings| !settings.show_title)
.unwrap_or(false)
.unwrap_or(category == "aside")
})
}

View File

@@ -312,6 +312,7 @@ fn mixed_source_languages_use_the_main_language_at_canonical_urls() {
let mut german_source = make_post("german-source", 1_710_086_400_000);
german_source.language = Some("de".into());
german_source.title = "Deutsche Quelle".into();
german_source.categories = vec!["aside".into()];
write_published_snapshot(&dir, &mut german_source, "Deutscher Quelltext");
bds_core::db::queries::post::insert_post(db.conn(), &german_source).unwrap();
let english_translation = PostTranslation {
@@ -386,6 +387,9 @@ fn mixed_source_languages_use_the_main_language_at_canonical_urls() {
assert!(canonical_german_source.contains("Deutscher Quelltext"));
assert!(localized_german_source.contains("English translation body"));
assert!(localized_fallback.contains("Unübersetzter Quelltext"));
let english_index = std::fs::read_to_string(output.join("en/index.html")).unwrap();
assert!(english_index.contains("English translation body"));
assert!(!english_index.contains(">English translation</a></h2>"));
assert!(
output
.join("2024/03/12/german-private/index.html")
@@ -997,6 +1001,87 @@ fn validation_apply_clears_unchanged_touched_post_routes() {
assert!(repaired.stale_pages.is_empty());
}
#[test]
fn validation_apply_expands_a_missing_post_to_its_aggregates() {
let (db, dir) = setup();
let mut metadata = make_metadata();
metadata.max_posts_per_page = 1;
let first = make_post("first", 1_710_000_000_000);
let second = make_post("second", 1_710_000_001_000);
generate_starter_site(
db.conn(),
dir.path(),
"p1",
&metadata,
&[PublishedPostSource {
post: first.clone(),
body_markdown: "First body".into(),
}],
"en",
)
.unwrap();
let posts = vec![
PublishedPostSource {
post: first,
body_markdown: "First body".into(),
},
PublishedPostSource {
post: second,
body_markdown: "Second body".into(),
},
];
let validation = bds_core::engine::validate_site::SiteValidationReport {
missing_pages: vec!["2024/03/09/second/index.html".into()],
extra_pages: Vec::new(),
stale_pages: Vec::new(),
};
let sections = sections_from_validation_report(&validation, &metadata);
apply_validation_sections(
db.conn(),
dir.path(),
"p1",
&metadata,
&posts,
&validation,
&sections,
)
.unwrap();
for path in [
"index.html",
"category/article/index.html",
"tag/rust/index.html",
"2024/index.html",
"2024/03/index.html",
"2024/03/09/index.html",
] {
assert!(
std::fs::read_to_string(dir.path().join(path))
.unwrap()
.contains("second"),
"aggregate {path} was not refreshed"
);
}
for path in [
"page/2/index.html",
"category/article/page/2/index.html",
"tag/rust/page/2/index.html",
"2024/page/2/index.html",
"2024/03/page/2/index.html",
"2024/03/09/page/2/index.html",
] {
assert!(
std::fs::read_to_string(dir.path().join(path))
.unwrap()
.contains("first"),
"aggregate pagination {path} was not refreshed"
);
}
}
#[test]
fn apply_validation_removes_extra_section_outputs() {
let (db, dir) = setup();