feat(cv): harden multi-page builder
Wrap pathological content, paginate oversized entries, measure A4 and Letter previews correctly, unify section ordering, and gate stored-output actions on saved state.
This commit is contained in:
@@ -96,6 +96,27 @@ public sealed class CvBuilderTests
|
||||
Assert.Contains(model.Sections, s => s.Title == "Volunteering" && s.Bullets.Contains("Coached juniors"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resolver_places_custom_sections_in_the_shared_section_order()
|
||||
{
|
||||
var settings = new CvVariantSettings
|
||||
{
|
||||
Sections =
|
||||
{
|
||||
new CvSectionSetting { Key = "custom:vol" },
|
||||
new CvSectionSetting { Key = "summary" },
|
||||
},
|
||||
CustomSections =
|
||||
{
|
||||
new CvCustomSectionSetting { Key = "vol", Title = "Volunteering", Items = { "Coached juniors" } },
|
||||
},
|
||||
};
|
||||
|
||||
var model = CvVariantResolver.Build(Rich(), settings, "F", null);
|
||||
Assert.True(model.Sections.FindIndex(section => section.Key == "custom:vol")
|
||||
< model.Sections.FindIndex(section => section.Key == "summary"));
|
||||
}
|
||||
|
||||
// ---- Renderer (one path, every theme is data) ----
|
||||
|
||||
[Fact]
|
||||
@@ -164,6 +185,31 @@ public sealed class CvBuilderTests
|
||||
Assert.False(CvThemeCatalog.Resolve("technical").AtsFriendly); // sidebar = not ATS-safe
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Long_content_wraps_and_can_flow_across_pages_without_shrinking_typography()
|
||||
{
|
||||
var profile = Rich();
|
||||
profile.Contact.FullName = new string('N', 180);
|
||||
profile.Contact.Email = $"{new string('e', 180)}@example.com";
|
||||
profile.Jobs[0].Title = new string('T', 220);
|
||||
profile.Jobs[0].Company = new string('C', 220);
|
||||
profile.Jobs[0].Bullets = Enumerable.Range(1, 7)
|
||||
.Select(index => index == 1 ? new string('x', 500) : $"Detailed achievement {index} with readable typography.")
|
||||
.ToList();
|
||||
|
||||
var renderer = new ThemedCvRenderer();
|
||||
var model = CvVariantResolver.Build(profile, new CvVariantSettings(), "F", null);
|
||||
var html = renderer.Render(model, CvThemeCatalog.Resolve("technical"), new CvVariantSettings { ThemeId = "technical" }).Html;
|
||||
|
||||
Assert.Contains("class=\"entry entry-flow\"", html);
|
||||
Assert.Contains("class=\"item-flow\"", html);
|
||||
Assert.Contains("overflow-wrap:anywhere", html);
|
||||
Assert.Contains("grid-template-columns:62mm minmax(0,1fr)", html);
|
||||
Assert.Contains("overflow:visible", html);
|
||||
Assert.Contains("white-space:normal", html);
|
||||
Assert.DoesNotContain("transform:scale", html);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accent_override_reaches_the_css()
|
||||
{
|
||||
|
||||
@@ -449,6 +449,36 @@ public sealed class JobApplicationsApplicationPackageTests
|
||||
Assert.Contains("curved", edinburgh.Html, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Template_renderer_wraps_long_content_and_escapes_sidebar_values()
|
||||
{
|
||||
var document = TailoredCvDraftJson.Normalize(new TailoredCvDocument
|
||||
{
|
||||
Headline = new string('H', 180),
|
||||
SelectedSkills = new List<string> { $"<script>alert(1)</script>{new string('s', 400)}" },
|
||||
Experience = new List<TailoredCvExperienceItem>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = new string('T', 220),
|
||||
Company = new string('C', 220),
|
||||
Start = "2020",
|
||||
End = "Present",
|
||||
Bullets = Enumerable.Range(1, 7).Select(index => index == 1 ? new string('x', 500) : $"Achievement {index}").ToList(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var html = new CvTemplateRenderer().Render(document, "auckland", new string('N', 180), "Engineer", "Acme", null).Html;
|
||||
|
||||
Assert.Contains("class=\"entry entry-flow\"", html);
|
||||
Assert.Contains("class=\"item-flow\"", html);
|
||||
Assert.Contains("grid-template-columns:34% minmax(0,66%)", html);
|
||||
Assert.Contains("overflow-wrap:anywhere", html);
|
||||
Assert.Contains("<script>alert(1)</script>", html);
|
||||
Assert.DoesNotContain("<script>", html);
|
||||
}
|
||||
|
||||
private static JobApplicationsController CreateController(JobTrackerContext db, ISummarizerService summarizer, string userId, ICvTemplateRenderer? renderer = null, ICvPdfExporter? exporter = null)
|
||||
{
|
||||
var user = db.Users.AsNoTracking().FirstOrDefault(x => x.Id == userId);
|
||||
|
||||
Reference in New Issue
Block a user