Upgraded to .NET 10 (#1147)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-01 20:32:16 +01:00
1 parent faae2951f7
commit fc7399e81b
327 files changed
+3059 -3049

No files matched your search

@@ -24,12 +24,12 @@ public sealed class ChosenLayoutsTests
[LayoutPurpose.CONTENT] = "title and content",
});
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(chosen.For(LayoutPurpose.CHAPTER)?.Id, Is.EqualTo("L2"));
Assert.That(chosen.For(LayoutPurpose.CONTENT), Is.Null, "Names are compared exactly, as the template's author wrote them.");
Assert.That(chosen.For(LayoutPurpose.TITLE), Is.Null, "A kind of slide without a choice is left to the model.");
});
}
}
[Test]
@@ -41,11 +41,11 @@ public sealed class ChosenLayoutsTests
[LayoutPurpose.TITLE] = string.Empty,
});
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(chosen, Is.SameAs(ChosenLayouts.NONE));
Assert.That(chosen.ToPromptRules(), Is.Empty, "Without choices, the system prompt gets no rules at all.");
});
}
}
[Test]
@@ -57,11 +57,11 @@ public sealed class ChosenLayoutsTests
var chosen = ChosenLayouts.Of(template, new Dictionary<LayoutPurpose, string> { [LayoutPurpose.CONTENT] = "Content" });
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Families.Select(family => family.Name), Is.EqualTo(new[] { "Content", "Content" }), "The test needs two families with the same name.");
Assert.That(chosen.For(LayoutPurpose.CONTENT)?.Id, Is.EqualTo("L1"));
});
}
}
[Test]
@@ -73,13 +73,13 @@ public sealed class ChosenLayoutsTests
[LayoutPurpose.CHAPTER] = "Section Header",
}).ToPromptRules();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(rules, Does.Contain("\n - L2 for each slide which starts a chapter;"));
Assert.That(rules, Does.Contain("\n - L4 for timelines, e.g., the course of a project."));
Assert.That(rules.IndexOf("L2 for", StringComparison.Ordinal), Is.LessThan(rules.IndexOf("L4 for", StringComparison.Ordinal)), "The rules follow the course of a presentation.");
Assert.That(rules, Does.Not.Contain("Section Header").And.Not.Contain("Two Content"), "No text of the template file reaches the system prompt.");
});
}
}
[Test]
@@ -92,11 +92,11 @@ public sealed class ChosenLayoutsTests
});
var rules = chosen.ToPromptRules();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(chosen.AddedSlides, Is.EqualTo(new[] { LayoutPurpose.IMPRINT }), "A timeline comes only where the content has one.");
Assert.That(rules, Does.Contain("\n - The presentation has at least one slide of each of these kinds, on its layout:\n - L3: the imprint, e.g., with the authors, the organization, and the date, as the very last slide;"));
Assert.That(rules, Does.Contain("leaves a slot empty rather than inventing anything."), "The model must not invent the details of an imprint.");
});
}
}
}
@@ -11,7 +11,7 @@ public sealed class LayoutPurposeTests
[Test]
public void EveryKindOfSlideIsDescribedForTheUserAndTheModel()
{
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
foreach (var purpose in Enum.GetValues<LayoutPurpose>())
{
@@ -19,7 +19,7 @@ public sealed class LayoutPurposeTests
Assert.That(purpose.Question(), Does.EndWith("?"), $"The kind of slide {purpose} has no question.");
Assert.That(purpose.PromptDescription(), Is.Not.Empty.And.Not.EndWith("."), $"The kind of slide {purpose} has no description, or one which ends where the rule for the model goes on.");
}
});
}
}
/// <summary>
@@ -43,10 +43,10 @@ public sealed class LayoutPurposeTests
public void TheKindsOfSlidesFollowTheOrderOfTheAreas()
{
var areas = Enum.GetValues<LayoutPurpose>().Select(purpose => purpose.Area()).ToList();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(areas, Is.Ordered, "The kinds of an area stand together, and the areas follow their own order.");
Assert.That(areas.Distinct(), Is.EquivalentTo(Enum.GetValues<LayoutPurposeArea>()), "Every area has kinds of slides.");
});
}
}
}
@@ -27,7 +27,7 @@ public sealed class PictureLibraryTests
var library = PictureLibrary.Read(this.folder, new Random(1), PictureLibrary.MAX_LIBRARY_PICTURES, null, CancellationToken.None);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(library.Pictures.Select(picture => picture.Name), Is.EquivalentTo(new[] { "rocket.png", "Moon.JPG", "mars.jpeg", "icon-star.png", "icons/flag.png", "IconSet/small/planet.png", "photos/crater.png" }), "Hidden files stay out, as everywhere in AI Studio.");
Assert.That(library.Pictures.Select(picture => picture.Name), Is.Ordered.Using<string>(StringComparer.OrdinalIgnoreCase));
@@ -36,7 +36,7 @@ public sealed class PictureLibraryTests
Assert.That(library.IconCount, Is.EqualTo(3));
Assert.That(library.SkippedFileCount, Is.EqualTo(2), "The SVG icon and the notes are neither PNG nor JPEG.");
Assert.That(library.IsSampled, Is.False);
});
}
}
[Test]
@@ -51,12 +51,12 @@ public sealed class PictureLibraryTests
var photoFolder = Path.Combine(this.folder, "iconography", "Photos");
this.CreateFiles("Icons/rocket.png", "Icons/planets/mars.png", "IconSet/moon.jpg", "iconography/Photos/crater.png");
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(PictureLibrary.Read(iconFolder, new Random(1), PictureLibrary.MAX_LIBRARY_PICTURES, null, CancellationToken.None).Pictures.Select(picture => picture.IsIcon), Is.EqualTo(new[] { true, true }));
Assert.That(PictureLibrary.Read(iconSetFolder + Path.DirectorySeparatorChar, new Random(1), PictureLibrary.MAX_LIBRARY_PICTURES, null, CancellationToken.None).IconCount, Is.EqualTo(1), "A trailing separator changes nothing.");
Assert.That(PictureLibrary.Read(photoFolder, new Random(1), PictureLibrary.MAX_LIBRARY_PICTURES, null, CancellationToken.None).IconCount, Is.Zero);
});
}
}
[Test]
@@ -67,7 +67,7 @@ public sealed class PictureLibraryTests
var sample = PictureLibrary.Read(this.folder, new Random(7), 10, null, CancellationToken.None);
var again = PictureLibrary.Read(this.folder, new Random(7), 10, null, CancellationToken.None);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(sample.Pictures, Has.Count.EqualTo(10));
Assert.That(sample.Pictures.Select(picture => picture.Name), Is.Unique);
@@ -75,7 +75,7 @@ public sealed class PictureLibraryTests
Assert.That(sample.PictureCount, Is.EqualTo(30), "The count tells about the whole library.");
Assert.That(sample.IsSampled, Is.True);
Assert.That(again.Pictures.Select(picture => picture.Name), Is.EqualTo(sample.Pictures.Select(picture => picture.Name)), "The same random numbers draw the same sample.");
});
}
}
[Test]
@@ -52,13 +52,13 @@ public sealed class PresentationFileTests
var result = await PresentationFile.CreateAsync(templatePath, targetPath, DECK, BasisOf(templatePath), OPTIONS, new NoPictures(), NullLogger.Instance, CancellationToken.None);
using var document = PresentationDocument.Open(targetPath, false);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(result.IsWritten, Is.True);
Assert.That(Directory.GetFileSystemEntries(this.targetFolder), Is.EqualTo(new[] { targetPath }), "Nothing else came about next to the chosen file.");
Assert.That(document.PresentationPart!.SlideParts.Count(), Is.EqualTo(2), "The chosen file holds the presentation instead of what it held before.");
Assert.That(TemporaryFiles(), Is.EquivalentTo(temporaryFiles), "The copy of the template is gone.");
});
}
}
[Test]
@@ -73,12 +73,12 @@ public sealed class PresentationFileTests
var result = await PresentationFile.CreateAsync(templatePath, targetPath, DECK, new Dictionary<string, string>(), OPTIONS, new NoPictures(), NullLogger.Instance, CancellationToken.None);
var targetContent = await File.ReadAllBytesAsync(targetPath);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(result.IsWritten, Is.False);
Assert.That(targetContent, Is.EqualTo(PREVIOUS_CONTENT));
Assert.That(TemporaryFiles(), Is.EquivalentTo(temporaryFiles), "The copy of the template is gone.");
});
}
}
[Test]
@@ -90,11 +90,11 @@ public sealed class PresentationFileTests
var result = await PresentationFile.CreateAsync(templatePath, templatePath, DECK, basis, OPTIONS, new NoPictures(), NullLogger.Instance, CancellationToken.None);
using var document = PresentationDocument.Open(templatePath, false);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(result.IsWritten, Is.True);
Assert.That(document.PresentationPart!.SlideParts.Count(), Is.EqualTo(2), "The template was copied before it was written over.");
});
}
}
private async Task<string> CreateTemplateAsync()
@@ -28,13 +28,13 @@ public sealed class PresentationTemplateTests
new TestLayout("B Content 1", TestPlaceholder.Title(), TestPlaceholder.Text(1, FULL_TEXT)),
new TestLayout("C Content 1", TestPlaceholder.Title(), TestPlaceholder.Text(1, FULL_TEXT)));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Families.Select(family => family.Id), Is.EqualTo(new[] { "L1", "L2" }));
Assert.That(template.Families.Select(family => family.Name), Is.EqualTo(new[] { "Title 1", "Content 1" }), "The model reads the name the variants share, without the word which tells them apart.");
Assert.That(template.Families[1].Variants.Select(variant => variant.Label), Is.EqualTo(new[] { "A", "B", "C" }));
Assert.That(template.VariantLabels, Is.EqualTo(new[] { "A", "B", "C" }));
});
}
}
[Test]
@@ -49,12 +49,12 @@ public sealed class PresentationTemplateTests
new TestLayout("B Table 2", TestPlaceholder.Title(), TestPlaceholder.Table(11, FULL_TEXT)),
new TestLayout("C Table 1", TestPlaceholder.Title(), TestPlaceholder.Table(11, FULL_TEXT)));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Families.Select(family => family.Name), Is.EqualTo(new[] { "Table 1", "Table 2" }));
Assert.That(template.Families[0].Variants.Select(variant => variant.Label), Is.EqualTo(new[] { "A", "B", "C" }));
Assert.That(template.Families[1].Variants.Select(variant => variant.Label), Is.EqualTo(new[] { "A", "B" }));
});
}
}
[Test]
@@ -66,11 +66,11 @@ public sealed class PresentationTemplateTests
new TestLayout("Content blue", TestPlaceholder.Title(), TestPlaceholder.Text(1, FULL_TEXT)),
new TestLayout("Content green", TestPlaceholder.Title(), TestPlaceholder.Text(1, FULL_TEXT)));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Families.Select(family => family.Name), Is.EqualTo(new[] { "Title", "Content" }));
Assert.That(template.VariantLabels, Is.EqualTo(new[] { "blue", "green" }));
});
}
}
[Test]
@@ -80,12 +80,12 @@ public sealed class PresentationTemplateTests
new TestLayout("Title Slide", TestPlaceholder.Title(), TestPlaceholder.Subtitle(SUBTITLE)),
new TestLayout("Two Content", TestPlaceholder.Title(), TestPlaceholder.Text(1, LEFT_TEXT), TestPlaceholder.Text(2, RIGHT_TEXT)));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Families.Select(family => family.Name), Is.EqualTo(new[] { "Title Slide", "Two Content" }));
Assert.That(template.Families.SelectMany(family => family.Variants).Select(variant => variant.Label), Is.All.Null);
Assert.That(template.VariantLabels, Is.Empty);
});
}
}
[Test]
@@ -115,11 +115,11 @@ public sealed class PresentationTemplateTests
new TestLayout("Content", TestPlaceholder.Title(), TestPlaceholder.Text(1, FULL_TEXT)));
var title = template.Families[0].Slots[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(title.Role, Is.EqualTo(PlaceholderRole.TITLE));
Assert.That(title.Bounds, Is.EqualTo(TestTemplate.MASTER_TITLE_BOUNDS));
});
}
}
[Test]
@@ -130,11 +130,11 @@ public sealed class PresentationTemplateTests
new TestLayout("Two Content", TestPlaceholder.Title(), TestPlaceholder.Text(2, RIGHT_TEXT), TestPlaceholder.Text(1, LEFT_TEXT)));
var texts = template.Families[0].Slots.Where(slot => slot.Role is PlaceholderRole.TEXT).ToList();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(texts.Select(slot => slot.Ordinal), Is.EqualTo(new[] { 1, 2 }));
Assert.That(texts.Select(slot => slot.Bounds), Is.EqualTo(new[] { LEFT_TEXT, RIGHT_TEXT }));
});
}
}
[Test]
@@ -171,11 +171,11 @@ public sealed class PresentationTemplateTests
new TestLayout("Picture with text", TestPlaceholder.Title(), TestPlaceholder.Text(1, LEFT_TEXT, 2000), TestPlaceholder.Picture(20, RIGHT_PICTURE)));
var catalog = template.ToCatalogPrompt();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(catalog, Does.Contain("landscape, backdrop)"), "The model learns where an icon must not go.");
Assert.That(catalog, Does.Contain("picture 1 (right, middle, landscape)"));
});
}
}
[Test]
@@ -183,12 +183,12 @@ public sealed class PresentationTemplateTests
{
var template = PresentationTemplate.CreateDefault();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Families.Select(family => family.Name), Is.EqualTo(new[] { "Title Slide", "Section Header", "Title and Content", "Two Content", "Content with Picture", "Table" }));
Assert.That(template.VariantLabels, Is.Empty);
Assert.That(template.ToCatalogPrompt(), Does.Contain("""- L3 "Title and Content": title (up to 1 line of about 36 characters); text 1 (full width, middle, up to 9 lines of about 58 characters)"""));
});
}
}
[Test]
@@ -199,13 +199,13 @@ public sealed class PresentationTemplateTests
new TestLayout("B Content", TestPlaceholder.Title(), TestPlaceholder.Text(1, FULL_TEXT)));
var family = template.FindFamily(" l1 ");
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(family, Is.Not.Null, "Models write IDs in whatever case they like.");
Assert.That(family?.GetLayout("B").Name, Is.EqualTo("B Content"));
Assert.That(family?.GetLayout("Z").Name, Is.EqualTo("A Content"), "A variant the family lacks falls back to its first one.");
Assert.That(template.FindFamily("L9"), Is.Null);
});
}
}
[Test]
@@ -222,7 +222,7 @@ public sealed class PresentationTemplateTests
new TestLayout("B Content", TestPlaceholder.Title(), TestPlaceholder.Text(1, FULL_TEXT), TestPlaceholder.Footer(12, LABEL_BELOW_RIGHT_PICTURE)));
var template = PresentationTemplate.Read(file);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Slides.Select(slide => slide.Number), Is.EqualTo(new[] { 1, 2 }));
Assert.That(template.Slides.Select(slide => slide.Title), Is.EqualTo(new[] { "Consultation hour", "News" }));
@@ -233,7 +233,7 @@ public sealed class PresentationTemplateTests
Assert.That(template.FindSlide(2), Is.SameAs(template.Slides[1]));
Assert.That(template.FindSlide(0), Is.Null);
Assert.That(template.FindSlide(3), Is.Null);
});
}
}
[Test]
@@ -262,7 +262,7 @@ public sealed class PresentationTemplateTests
new TestLayout("Table", TestPlaceholder.Title(), TestPlaceholder.Table(11, FULL_TEXT)));
var template = PresentationTemplate.Read(file);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Slides[0].Contents.Select(content => (content.Slot.Role, content.Text)), Is.EqualTo(new[] { (PlaceholderRole.TITLE, "Costs"), (PlaceholderRole.TABLE, "Item | Cost\nFuel | 10") }), "A table pasted onto the slide is the table of the slide.");
Assert.That(template.Slides[1].Contents.Select(content => (content.Slot.Role, content.Text)), Is.EqualTo(new[] { (PlaceholderRole.TITLE, "Costs"), (PlaceholderRole.TABLE, "Item | Cost\nFood | 5") }), "An empty table placeholder makes way for the table the slide shows.");
@@ -271,7 +271,7 @@ public sealed class PresentationTemplateTests
- Template slide 1, to edit, with the slots of layout L1: title "Costs"; table 1 "Item | Cost / Fuel | 10"
- Template slide 2, to keep unchanged: title "Costs"; table 1 "Item | Cost / Food | 5"
""".ReplaceLineEndings("\n")), "The model learns that the slide has a table.");
});
}
}
[Test]
@@ -306,7 +306,7 @@ public sealed class PresentationTemplateTests
var template = PresentationTemplate.Read(file);
var slots = template.Families[0].Slots;
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Slides[0].MovedSlots.Select(slot => (slot.Role, slot.Ordinal, slot.Bounds, slot.Capacity)), Is.EqualTo(new[] { (PlaceholderRole.TEXT, 1, half, (TextCapacity?)new TextCapacity(5, 25)) }), "Half the size holds half the lines of half the characters.");
Assert.That(template.Slides[0].OnSlide(slots[1]).Capacity, Is.EqualTo(new TextCapacity(5, 25)));
@@ -319,7 +319,7 @@ public sealed class PresentationTemplateTests
- Template slide 3, to edit, with the slots of layout L1: title "Crew"; text 1 (left, middle, up to 5 lines of about 25 characters, on this slide, empty)
""".ReplaceLineEndings("\n")));
Assert.That(template.ToTemplateSlidesPrompt([TemplateSlideUse.KEEP]), Is.EqualTo("- Template slide 1, to keep unchanged: title \"Rocket\"; text 1 \"Reusable\""), "A slide to keep needs no sizes.");
});
}
}
[Test]
@@ -335,11 +335,11 @@ public sealed class PresentationTemplateTests
new TestLayout("Table", TestPlaceholder.Title(), TestPlaceholder.Table(11, FULL_TEXT)));
var template = PresentationTemplate.Read(file);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Slides[0].MovedSlots, Is.Empty, "A table grows with its rows, so a short one at the place of its slot does not shrink the slot.");
Assert.That(template.Slides[1].MovedSlots.Select(slot => slot.Bounds), Is.EqualTo(new[] { lower with { Height = FULL_TEXT.Bottom - lower.Y } }), "A table placed lower leaves less room, down to the bottom of its slot.");
});
}
}
[Test]
@@ -368,7 +368,7 @@ public sealed class PresentationTemplateTests
new TestLayout("Table", TestPlaceholder.Title(), TestPlaceholder.Table(11, FULL_TEXT)));
var elements = PresentationTemplate.Read(file).Slides[0].Elements;
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(elements.Select(element => (element.Number, element.ShapeId, element.Kind, element.Text)), Is.EqualTo(new[]
{
@@ -382,7 +382,7 @@ public sealed class PresentationTemplateTests
}), "The footer belongs to the presentation, the empty text box shows nothing, and the first table is the content of the table slot.");
Assert.That(elements.Select(element => element.Bounds), Is.EqualTo(new SlotBounds?[] { RIGHT_PICTURE, LABEL_BELOW_RIGHT_PICTURE, RIGHT_TEXT, line, LEFT_TEXT, RIGHT_TEXT with { Height = 2 * 370_840 }, LEFT_TEXT }));
Assert.That(elements.Select(element => element.Name), Has.Exactly(1).EqualTo("element 3"));
});
}
}
[Test]
@@ -406,7 +406,7 @@ public sealed class PresentationTemplateTests
new TestLayout("Content", TestPlaceholder.Title(), TestPlaceholder.Text(1, LEFT_TEXT)));
var template = PresentationTemplate.Read(file);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.ToTemplateSlidesPrompt([TemplateSlideUse.EDIT, TemplateSlideUse.EDIT]), Is.EqualTo(
"""
@@ -414,7 +414,7 @@ public sealed class PresentationTemplateTests
- Template slide 2, to edit, with the slots of layout L1: title "Chart"; element 1 (chart, left, middle, overlaps text 1)
""".ReplaceLineEndings("\n")), "A slot the slide leaves out takes the place of the layout, where filling it would cover the chart.");
Assert.That(template.ToTemplateSlidesPrompt([TemplateSlideUse.KEEP]), Is.EqualTo("- Template slide 1, to keep unchanged: title \"Rocket\"; text 1 \"Reusable\""), "A slide to keep has nothing to remove.");
});
}
}
[Test]
@@ -423,7 +423,7 @@ public sealed class PresentationTemplateTests
using var file = TestTemplate.Create(new TestTemplateOptions(Slides: [StackedHeadings.Slide()]), StackedHeadings.Layout());
var slide = PresentationTemplate.Read(file).Slides[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(slide.SlotCopies.Select(copy => (copy.Slot.Role, copy.Slot.Ordinal, copy.Slot.Bounds, copy.ShapeId)), Is.EqualTo(new[]
{
@@ -445,7 +445,7 @@ public sealed class PresentationTemplateTests
(PlaceholderRole.TEXT, "Consectetuer"),
}));
Assert.That(slide.Elements.Select(element => (element.Kind, element.Text)), Is.EqualTo(new[] { (TemplateSlideElementKind.PICTURE, string.Empty), (TemplateSlideElementKind.TEXT_BOX, "Bildquelle hier angeben") }), "The source of the picture has the height of a heading, yet lines up with none.");
});
}
}
[Test]
@@ -472,11 +472,11 @@ public sealed class PresentationTemplateTests
StackedHeadings.Layout());
var slide = PresentationTemplate.Read(file).Slides[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(slide.SlotCopies.Select(copy => (copy.Slot.Role, copy.Slot.Ordinal, copy.Slot.Bounds)), Is.EqualTo(new[] { (PlaceholderRole.TEXT, 2, twin) }), "Of two copies in one place, the first counts.");
Assert.That(slide.Elements.Select(element => element.Text), Is.EqualTo(new[] { "On text 1", "Other width", "Twin", "Bildquelle hier angeben" }), "A box lying on a slot, a text of another width, the second twin and a box in no line with a slot stay elements; one whose ID the title shares and an empty one are neither.");
});
}
}
[Test]
@@ -497,11 +497,11 @@ public sealed class PresentationTemplateTests
StackedHeadings.Layout());
var slide = PresentationTemplate.Read(file).Slides[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(slide.SlotCopies.Select(copy => (copy.Slot.Ordinal, copy.Slot.Bounds)), Is.EqualTo(new[] { (2, upperRight), (3, lowerRight) }));
Assert.That(slide.Slots.Select(slot => $"{slot.Role.CatalogName()} {slot.Ordinal}"), Is.EqualTo(new[] { "title 1", "label 1", "text 2", "text 1", "text 3" }), "The slots of the layout keep their numbers; a copy goes where a reader comes across it.");
});
}
}
[Test]
@@ -521,14 +521,14 @@ public sealed class PresentationTemplateTests
using var file = TestTemplate.Create(new TestTemplateOptions(Slides: [StackedHeadings.Slide()]), StackedHeadings.Layout());
var template = PresentationTemplate.Read(file);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.ToTemplateSlidesPrompt([TemplateSlideUse.EDIT]), Is.EqualTo(
"""
- Template slide 1, to edit, with the slots of layout L1: title "Missions"; label 1 "Lorem ipsum"; text 1 (left, middle, up to 3 lines of about 25 characters, on this slide) "Ut wisi enim"; label 2 (above text 2, up to 1 line of about 14 characters, on this slide) "Lorem"; text 2 (left, middle, up to 3 lines of about 25 characters, on this slide) "Duis autem"; label 3 (above text 3, up to 1 line of about 18 characters, on this slide) "Nam liber"; text 3 (left, bottom, up to 3 lines of about 25 characters, on this slide) "Consectetuer"; element 1 (picture, right, middle); element 2 (text box, right, bottom) "Bildquelle hier angeben"
"""));
Assert.That(template.ToTemplateSlidesPrompt([TemplateSlideUse.KEEP]), Is.EqualTo("- Template slide 1, to keep unchanged: title \"Missions\"; label 1 \"Lorem ipsum\"; text 1 \"Ut wisi enim\"; label 2 \"Lorem\"; text 2 \"Duis autem\"; label 3 \"Nam liber\"; text 3 \"Consectetuer\""), "A slide to keep says what its copies hold, so that no new slide repeats it.");
});
}
}
[Test]
@@ -540,10 +540,10 @@ public sealed class PresentationTemplateTests
new TestLayout("Title Only", TestPlaceholder.Title()));
var template = PresentationTemplate.Read(file);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Slides[0].FamilyId, Is.Null);
Assert.That(template.Slides[0].Title, Is.EqualTo("Thank you"), "What the slide holds is read all the same, so the user knows which slide it is.");
});
}
}
}
@@ -51,12 +51,12 @@ public sealed class PresentationWriterTests
var (file, _) = await WriteAsync(DECK, OPTIONS, new TestTemplateOptions(ExampleSlides: 2), new FakePictureSource(WIDE_PICTURE, WIDE_PICTURE));
using var document = PresentationDocument.Open(file, false);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(document.PresentationPart!.SlideParts.Count(), Is.EqualTo(5), "The sample slides of the template are gone, parts included.");
Assert.That(SlidesOf(document).Select(TitleOf), Is.EqualTo(new[] { "Mars", "Why Mars", "The rocket", "Landing", "Costs" }), "The presentation holds the slides of the deck, nothing more.");
});
}
}
[Test]
@@ -66,11 +66,11 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var sections = document.PresentationPart!.Presentation!.Descendants<P14.Section>().ToList();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(sections.Select(section => section.Name?.Value), Is.EqualTo(new[] { "Mars", "Why Mars", "Landing" }), "The section of the template's samples is gone, and the title slide forms a section named after the presentation.");
Assert.That(sections.Select(section => section.Descendants<P14.SectionSlideIdListEntry>().Count()), Is.EqualTo(new[] { 1, 2, 2 }));
});
}
}
[Test]
@@ -83,14 +83,14 @@ public sealed class PresentationWriterTests
var slide = SlidesOf(document)[1].Slide!;
var placeholders = slide.Descendants<P.PlaceholderShape>().Select(placeholder => (placeholder.Type?.InnerText ?? "obj", placeholder.Index?.Value)).ToList();
var textBody = slide.Descendants<P.Shape>().Single(shape => shape.Descendants<P.PlaceholderShape>().Single().Index?.Value == 1).TextBody!;
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(placeholders, Is.EqualTo(new (string, uint?)[] { ("title", null), ("obj", 1), ("ftr", 10), ("sldNum", 11) }));
Assert.That(textBody.Elements<A.Paragraph>().Select(paragraph => paragraph.ParagraphProperties?.Level?.Value ?? 0), Is.EqualTo(new[] { 0, 1 }));
Assert.That(textBody.InnerText, Is.EqualTo("Close to EarthTwo years per trip"));
Assert.That(slide.Descendants<P.Shape>().Any(shape => shape.InnerText == "Mission team"), Is.True, "The footer carries the text the user chose.");
Assert.That(slide.Descendants<A.Field>().Single().Type?.Value, Is.EqualTo("slidenum"), "PowerPoint fills in the number itself.");
});
}
}
[Test]
@@ -107,11 +107,11 @@ public sealed class PresentationWriterTests
var (chosen, _) = await WriteAsync(deck, OPTIONS with { VariantLabel = "B" });
var (unknown, _) = await WriteAsync(deck, OPTIONS with { VariantLabel = "Z" });
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(LayoutNamesOf(chosen), Is.EqualTo(new[] { "B Title", "B Content", "B Content", "B Content" }), "Chapters keep the variant.");
Assert.That(LayoutNamesOf(unknown), Is.EqualTo(new[] { "A Title", "A Content", "A Content", "A Content" }), "A variant the template lacks falls back to its first one.");
});
}
}
[Test]
@@ -129,7 +129,7 @@ public sealed class PresentationWriterTests
var backdrop = slides[3].Slide!.Descendants<P.Picture>().Single();
var crop = backdrop.BlipFill!.GetFirstChild<A.SourceRectangle>()!;
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(transform.Extents!.Cx!.Value, Is.EqualTo(RIGHT_PICTURE.Width));
Assert.That(transform.Extents.Cy!.Value, Is.EqualTo(RIGHT_PICTURE.Width / 4));
@@ -138,7 +138,7 @@ public sealed class PresentationWriterTests
Assert.That(crop.Right?.Value, Is.EqualTo(27_778));
Assert.That(crop.Top, Is.Null);
Assert.That(slides[2].ImageParts.Count(), Is.EqualTo(1));
});
}
}
[Test]
@@ -149,13 +149,13 @@ public sealed class PresentationWriterTests
// The icon lands in the backdrop, which the validator advises against, and keeps its edges anyway:
var icon = SlidesOf(document)[3].Slide!.Descendants<P.Picture>().Single();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(icon.BlipFill!.GetFirstChild<A.SourceRectangle>(), Is.Null);
Assert.That(icon.ShapeProperties!.Transform2D!.Extents!.Cx!.Value, Is.EqualTo(WHOLE_SLIDE.Width));
Assert.That(icon.ShapeProperties.Transform2D.Extents.Cy!.Value, Is.EqualTo(WHOLE_SLIDE.Width / 4));
Assert.That(ValidationErrorsOf(document), Is.Empty);
});
}
}
[Test]
@@ -164,12 +164,12 @@ public sealed class PresentationWriterTests
var (file, result) = await WriteAsync(DECK, OPTIONS, pictures: new FakePictureSource(WIDE_PICTURE with { ContentType = "image/webp" }, WIDE_PICTURE));
using var document = PresentationDocument.Open(file, false);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(result.SkippedPictures, Is.EqualTo(new[] { 1 }));
Assert.That(SlidesOf(document)[2].Slide!.Descendants<P.Picture>(), Is.Empty, "The slot stays empty rather than holding a picture nobody can see.");
Assert.That(ValidationErrorsOf(document), Is.Empty);
});
}
}
[Test]
@@ -186,14 +186,14 @@ public sealed class PresentationWriterTests
using var keptDocument = PresentationDocument.Open(kept, false);
var keptSlide = SlidesOf(keptDocument)[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(PicturePlaceholdersOf(SlidesOf(leftDocument)[0]), Is.Empty, "By default, no prompt text stays behind.");
Assert.That(PicturePlaceholdersOf(keptSlide), Has.Count.EqualTo(1));
Assert.That(PlaceholderShapeOf(keptSlide, "pic", 20).TextBody, Is.Null, "Without a text of its own, the placeholder shows the prompt text of the layout.");
Assert.That(keptSlide.ImageParts, Is.Empty);
Assert.That(ValidationErrorsOf(keptDocument), Is.Empty);
});
}
}
[Test]
@@ -203,13 +203,13 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var slide = SlidesOf(document)[2];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(result.SkippedPictures, Is.EqualTo(new[] { 1 }));
Assert.That(slide.Slide!.Descendants<P.Picture>(), Is.Empty);
Assert.That(PicturePlaceholdersOf(slide), Has.Count.EqualTo(1), "The user can insert the picture there themselves.");
Assert.That(ValidationErrorsOf(document), Is.Empty);
});
}
}
[Test]
@@ -219,13 +219,13 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var table = SlidesOf(document)[4].Slide!.Descendants<A.Table>().Single();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(table.TableProperties!.GetFirstChild<A.TableStyleId>()?.Text, Is.EqualTo(TestTemplate.DEFAULT_TABLE_STYLE_ID));
Assert.That(table.TableProperties.FirstRow?.Value, Is.True, "The headers are the first row.");
Assert.That(table.Elements<A.TableRow>().Select(row => row.InnerText), Is.EqualTo(new[] { "ItemCost", "Fuel10" }));
Assert.That(table.TableGrid!.Elements<A.GridColumn>().Sum(column => column.Width!.Value), Is.EqualTo(TestTemplate.MASTER_BODY_BOUNDS.Width), "The columns span the slot exactly.");
});
}
}
[Test]
@@ -234,11 +234,11 @@ public sealed class PresentationWriterTests
var (file, _) = await WriteAsync(DECK, OPTIONS, new TestTemplateOptions(AsTemplate: true));
using var document = PresentationDocument.Open(file, false);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(document.DocumentType, Is.EqualTo(PresentationDocumentType.Presentation));
Assert.That(ValidationErrorsOf(document), Is.Empty);
});
}
}
[Test]
@@ -257,7 +257,7 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var slides = SlidesOf(document);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(slides.Select(TitleOf), Is.EqualTo(new[] { "Consultation hour", "Agenda", "News", "Questions", "Impressum" }));
@@ -265,7 +265,7 @@ public sealed class PresentationWriterTests
Assert.That(BodyParagraphsOf(slides[2]).Select(paragraph => paragraph.InnerText), Is.EqualTo(new[] { "AI Studio:", "EINSTEIN:" }));
Assert.That(slides[3].Slide!.Show, Is.Null, "The user chose the slide, so it shows although the template hides it.");
Assert.That(document.PresentationPart.Presentation!.SlideIdList!.Elements<P.SlideId>().Select(slideId => slideId.Id!.Value), Is.Unique);
});
}
}
[Test]
@@ -283,13 +283,13 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var sections = document.PresentationPart!.Presentation!.Descendants<P14.Section>().ToList();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(SlidesOf(document).Select(TitleOf), Is.EqualTo(new[] { "Consultation hour", "Impressum", "Agenda", "News" }), "An imprint is a slide like any other: nothing moves it to the end.");
Assert.That(sections.Select(section => section.Name?.Value), Is.EqualTo(new[] { "Weekly", "Agenda" }));
Assert.That(sections.Select(section => section.Descendants<P14.SectionSlideIdListEntry>().Count()), Is.EqualTo(new[] { 2, 2 }));
});
}
}
[Test]
@@ -302,7 +302,7 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(withFooter, false);
var slides = SlidesOf(document);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(slides.Select(slide => slide.SlideLayoutPart?.SlideLayout?.CommonSlideData?.Name?.Value), Is.EqualTo(new[] { "B Title", "B Content" }));
@@ -310,7 +310,7 @@ public sealed class PresentationWriterTests
Assert.That(FootersOf(slides[0]), Is.EqualTo(new[] { "Mission team" }));
Assert.That(FootersOf(slides[1]), Is.EqualTo(new[] { "Mission team" }));
Assert.That(FootersOfSlide(withoutFooter, 1), Is.EqualTo(new[] { "Old footer" }), "Without a footer of the presentation, a kept slide keeps its own.");
});
}
}
[Test]
@@ -332,13 +332,13 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(output, false);
var slide = SlidesOf(document)[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(slide.SlideLayoutPart?.SlideLayout?.CommonSlideData?.Name?.Value, Is.EqualTo("B Content"));
Assert.That(slide.Slide!.Descendants<P.PlaceholderShape>().Select(placeholder => (placeholder.Type?.InnerText ?? "obj", placeholder.Index?.Value)), Is.EqualTo(new (string, uint?)[] { ("title", null), ("obj", 2), ("ftr", 12) }));
Assert.That(FootersOf(slide), Is.EqualTo(new[] { "Mission team" }));
});
}
}
[Test]
@@ -364,7 +364,7 @@ public sealed class PresentationWriterTests
var slide = SlidesOf(document)[0];
var paragraphs = BodyParagraphsOf(slide);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(TitleOf(slide), Is.EqualTo("News of the week"));
@@ -376,7 +376,7 @@ public sealed class PresentationWriterTests
Assert.That(paragraphs[0].Elements<A.Run>().Single().RunProperties?.FontSize, Is.Null, "A main point keeps what the author chose for main points.");
Assert.That(PlaceholderShapeOf(slide, "body", 17).TextBody!.InnerText, Is.EqualTo("This week"), "A slot the template slide left out gets its placeholder from the layout.");
Assert.That(slide.Slide!.Descendants<P.NonVisualDrawingProperties>().Select(properties => properties.Id!.Value), Is.Unique);
});
}
}
[Test]
@@ -388,11 +388,11 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var slide = SlidesOf(document)[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(TitleOf(slide), Is.EqualTo("News"));
Assert.That(BodyParagraphsOf(slide).Select(paragraph => paragraph.InnerText), Is.EqualTo(new[] { "AI Studio:", "EINSTEIN:" }));
});
}
}
[Test]
@@ -410,7 +410,7 @@ public sealed class PresentationWriterTests
using (var stream = slide.ImageParts.Single().GetStream(FileMode.Open, FileAccess.Read))
stream.CopyTo(pictureData);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(TitleOf(slide), Is.EqualTo("The rocket"));
@@ -418,7 +418,7 @@ public sealed class PresentationWriterTests
Assert.That(slide.Slide!.Descendants<P.Picture>().Count(), Is.EqualTo(1));
Assert.That(pictureData.ToArray(), Is.EqualTo(new byte[] { 4, 5, 6 }), "The old picture leaves the file along with its shape.");
Assert.That(slide.Slide!.Descendants<P.Picture>().Single().ShapeProperties!.Transform2D!.Extents!.Cy!.Value, Is.EqualTo(RIGHT_PICTURE.Height), "The old picture was fit into its slot, which therefore stays as large as the layout makes it.");
});
}
}
[Test]
@@ -433,12 +433,12 @@ public sealed class PresentationWriterTests
var transform = SlidesOf(document)[0].Slide!.Descendants<P.Picture>().Single().ShapeProperties!.Transform2D!;
var bounds = new SlotBounds(transform.Offset!.X!.Value, transform.Offset.Y!.Value, transform.Extents!.Cx!.Value, transform.Extents.Cy!.Value);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(moved.Contains(bounds, 0, 0), Is.True, $"The picture fits into the slot where the slide put it, but lies at {bounds}.");
Assert.That(bounds.Width, Is.EqualTo(moved.Width), "The wide picture takes the whole width of the moved slot.");
});
}
}
[Test]
@@ -457,7 +457,7 @@ public sealed class PresentationWriterTests
var table = shapeTree.Elements<P.GraphicFrame>().Single();
var note = shapeTree.Elements<P.Shape>().Single(shape => shape.TextBody?.InnerText == "Source: last year");
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(RowsOf(table), Is.EqualTo(new[] { "Item | Cost", "Fuel | 10", "Food | 5" }), "No row of the old table is left below the shorter new one.");
@@ -465,7 +465,7 @@ public sealed class PresentationWriterTests
Assert.That(shapeTree.Elements().ToList().IndexOf(table), Is.LessThan(shapeTree.Elements().ToList().IndexOf(note)), "The new table lies where the old one did, behind what lay in front of it.");
Assert.That(RowsOf(slides[1].Slide!.Descendants<P.GraphicFrame>().Single()), Is.EqualTo(new[] { "Step | Date", "Launch | 2030" }));
Assert.That(slides[1].Slide!.Descendants<P.PlaceholderShape>().Count(placeholder => placeholder.Type?.InnerText == "tbl"), Is.EqualTo(1), "The empty placeholder the free table stood in for is gone.");
});
}
}
[Test]
@@ -477,11 +477,11 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var slide = SlidesOf(document)[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(TitleOf(slide), Is.EqualTo("New costs"));
Assert.That(RowsOf(slide.Slide!.Descendants<P.GraphicFrame>().Single()), Has.Count.EqualTo(6));
});
}
}
[Test]
@@ -493,7 +493,7 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var slide = SlidesOf(document)[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(BodyParagraphsOf(slide).Select(paragraph => paragraph.InnerText), Is.EqualTo(new[] { "A new release" }));
@@ -503,7 +503,7 @@ public sealed class PresentationWriterTests
Assert.That(slide.Slide.Descendants<P.PlaceholderShape>().Where(placeholder => placeholder.Index?.Value == 17), Is.Empty, "The label of the template is gone, with its sample text.");
Assert.That(slide.Slide.Descendants<A.SolidFill>().Count(), Is.EqualTo(1), "What the model does not remove stays.");
Assert.That(slide.ChartParts.Count(), Is.EqualTo(1));
});
}
}
[Test]
@@ -517,7 +517,7 @@ public sealed class PresentationWriterTests
var slide = SlidesOf(document)[0];
var secondHeading = ShapeWithIdOf(slide, 5);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(PlaceholderShapeOf(slide, "body", 17).TextBody!.InnerText, Is.EqualTo("Mars"));
@@ -528,7 +528,7 @@ public sealed class PresentationWriterTests
Assert.That(slide.Slide!.Descendants<P.NonVisualDrawingProperties>().Select(properties => properties.Id!.Value), Has.None.EqualTo(7U).And.None.EqualTo(8U), "The third heading and its text are removed.");
Assert.That(slide.Slide.Descendants<P.Shape>().Select(shape => shape.TextBody?.InnerText), Has.Some.EqualTo("Bildquelle hier angeben"), "What the model does not remove stays.");
Assert.That(slide.Slide.Descendants<P.Picture>().Count(), Is.EqualTo(1));
});
}
}
[Test]
@@ -541,7 +541,7 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var slide = SlidesOf(document)[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(slide.SlideLayoutPart?.SlideLayout?.CommonSlideData?.Name?.Value, Is.EqualTo("B Content"));
@@ -549,7 +549,7 @@ public sealed class PresentationWriterTests
Assert.That(ShapeWithIdOf(slide, 5).TextBody!.InnerText, Is.EqualTo("Moon"));
Assert.That(ShapeWithIdOf(slide, 7).TextBody!.InnerText, Is.EqualTo("Venus"));
Assert.That(ShapeWithIdOf(slide, 6).TextBody!.InnerText, Is.EqualTo("Duis autem"), "A copy the slide leaves empty keeps the content of the template.");
});
}
}
[Test]
@@ -561,13 +561,13 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var slide = SlidesOf(document)[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(slide.Slide!.Descendants<P.GraphicFrame>(), Is.Empty);
Assert.That(slide.ChartParts, Is.Empty);
Assert.That(slide.ImageParts.Count(), Is.EqualTo(1), "The picture nobody removed keeps its part.");
});
}
}
[Test]
@@ -586,12 +586,12 @@ public sealed class PresentationWriterTests
using var document = PresentationDocument.Open(file, false);
var slides = SlidesOf(document);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ValidationErrorsOf(document), Is.Empty);
Assert.That(slides[0].Slide!.Timing, Is.Null, "An animation of a shape which is gone would break the slide.");
Assert.That(slides[1].Slide!.Timing, Is.Not.Null, "The animated shape is still there.");
});
}
}
/// <summary>
@@ -21,14 +21,14 @@ public sealed class SlideDeckSpecTests
""";
Assert.That(SlideDeckSpec.TryParse(ANSWER, out var spec, out var issue), Is.True, issue);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(spec.Title, Is.EqualTo("Mars"));
Assert.That(spec.Slides, Has.Count.EqualTo(1));
Assert.That(spec.Slides[0].Layout, Is.EqualTo("L3"));
Assert.That(spec.Slides[0].Title, Is.EqualTo("Why Mars"));
Assert.That(spec.Slides[0].Texts[0].Paragraphs, Is.EqualTo(new[] { new SlideParagraph("Close to Earth", 0), new SlideParagraph("Two years per trip", 1) }));
});
}
}
[Test]
@@ -54,7 +54,7 @@ public sealed class SlideDeckSpecTests
Assert.That(SlideDeckSpec.TryParse(ANSWER, out var spec, out var issue), Is.True, issue);
var slide = spec.Slides[0];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(slide.StartsChapter, Is.True);
Assert.That(slide.Subtitle, Is.Empty, "A null is as good as a missing value.");
@@ -63,17 +63,17 @@ public sealed class SlideDeckSpecTests
Assert.That(slide.Pictures, Is.EqualTo(new[] { 2 }));
Assert.That(slide.Table?.Headers, Is.EqualTo(new[] { "Year", "Missions" }));
Assert.That(slide.Table?.Rows, Is.EqualTo(new[] { new[] { "2024", "3" }, new[] { "2025", string.Empty } }), "Numbers in a table are text on a slide.");
});
}
}
[Test]
public void AnAnswerWithoutJsonIsReported()
{
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpec.TryParse("I cannot help with that.", out _, out var issue), Is.False);
Assert.That(issue, Does.Contain("no complete JSON object"));
});
}
}
[Test]
@@ -81,11 +81,11 @@ public sealed class SlideDeckSpecTests
{
const string ANSWER = """{ "title": "Mars", "slides": [ { "layout": "L1",, "title": "Mars" } ] }""";
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpec.TryParse(ANSWER, out _, out var issue), Is.False);
Assert.That(issue, Does.StartWith("The JSON object cannot be read"), "The reader's own message follows, which tells the model where the JSON breaks.");
});
}
}
[Test]
@@ -141,12 +141,12 @@ public sealed class SlideDeckSpecTests
""";
Assert.That(SlideDeckSpec.TryParse(ANSWER, out var spec, out var issue), Is.True, issue);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(spec.Slides.Select(slide => slide.TemplateSlide), Is.EqualTo(new int?[] { 1, 2, null }));
Assert.That(spec.Slides.Select(slide => slide.HasContent), Is.EqualTo(new[] { false, true, true }), "Only content of its own makes a slide more than a reference.");
Assert.That(spec.Slides[0].Layout, Is.Empty);
});
}
}
[Test]
@@ -161,13 +161,13 @@ public sealed class SlideDeckSpecTests
""";
Assert.That(SlideDeckSpec.TryParse(ANSWER, out var spec, out var issue), Is.True, issue);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(spec.Slides[0].Removals, Is.EqualTo(new[] { "element 2", "label 1", "3" }), "Every name counts once, whatever its case.");
Assert.That(spec.Slides[1].Removals, Is.EqualTo(new[] { "element 1" }), "A single name is as good as a list.");
Assert.That(spec.Slides[2].Removals, Is.Empty);
Assert.That(spec.Slides.Select(slide => slide.HasContent), Is.EqualTo(new[] { true, true, false }), "Removing something changes a template slide as well.");
});
}
}
[Test]
@@ -49,13 +49,13 @@ public sealed class SlideDeckSpecValidatorTests
]);
var issues = SlideDeckSpecValidator.Validate(deck, TEMPLATE, 1, 0);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(issues, Has.Some.Contains("Slide 1 has 2 texts, but layout L3 has only 1"));
Assert.That(issues, Has.Some.Contains("Slide 2 has a subtitle, but layout L3 has none"));
Assert.That(issues, Has.Some.Contains("Slide 3 has 1 picture, but layout L3 has no picture"));
Assert.That(issues, Has.Some.Contains("Slide 4 has a table, but layout L3 has none"));
});
}
}
[Test]
@@ -78,11 +78,11 @@ public sealed class SlideDeckSpecValidatorTests
var fitting = Slide("L3", texts: [Text(Enumerable.Range(1, 10).Select(number => $"Point {number}").ToArray())]);
var tooLong = Slide("L3", texts: [Text(Enumerable.Range(1, 11).Select(number => $"Point {number}").ToArray())]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.Validate(new SlideDeckSpec("Mars", [fitting]), TEMPLATE, 0, 0), Is.Empty);
Assert.That(SlideDeckSpecValidator.Validate(new SlideDeckSpec("Mars", [tooLong]), TEMPLATE, 0, 0), Has.Some.Contains("Slide 1: text 1 needs about 11 lines, but only about 9 fit"));
});
}
}
[Test]
@@ -90,11 +90,11 @@ public sealed class SlideDeckSpecValidatorTests
{
var deck = new SlideDeckSpec("Mars", [Slide("L5", texts: [Text("Reusable")], pictures: [2])]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.Validate(deck, TEMPLATE, 1, 0), Has.Some.Contains("only pictures 1 to 1 are attached"));
Assert.That(SlideDeckSpecValidator.Validate(deck, TEMPLATE, 0, 0), Has.Some.Contains("no pictures are attached"));
});
}
}
[Test]
@@ -110,11 +110,11 @@ public sealed class SlideDeckSpecValidatorTests
{
var deck = new SlideDeckSpec("Mars", [Slide("L3", texts: [Text("One")])]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.Validate(deck, TEMPLATE, 0, 3), Has.Some.Contains("exactly 3 were asked for"));
Assert.That(SlideDeckSpecValidator.Validate(deck, TEMPLATE, 0, 0), Is.Empty, "0 leaves the number to the model.");
});
}
}
[Test]
@@ -129,11 +129,11 @@ public sealed class SlideDeckSpecValidatorTests
Edit(4, texts: [Text("Thema:\tWeekly", "Datum:\t2026-10-19")]),
]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.Validate(deck, TemplateWithSlides(), 0, 5, USES), Is.Empty, "Every template slide counts as a slide of the presentation, an imprint included.");
Assert.That(SlideDeckSpecValidator.Validate(deck, TemplateWithSlides(), 0, 4, USES), Has.Some.Contains("has 5 slides, but exactly 4 were asked for"));
});
}
}
[Test]
@@ -142,13 +142,13 @@ public sealed class SlideDeckSpecValidatorTests
var deck = new SlideDeckSpec("Weekly", [Keep(1), Keep(1), Keep(5), Edit(4, texts: [Text("Thema:\tWeekly")])]);
var issues = SlideDeckSpecValidator.Validate(deck, TemplateWithSlides(), 0, 0, USES);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(issues, Has.Some.Contains("Slide 3 refers to template slide 5, which is not offered. Refer only to template slides 1, 2, 3, 4."));
Assert.That(issues, Has.Some.Contains("Template slide 1 appears 2 times."));
Assert.That(issues, Has.Some.Contains("Template slide 2 is missing. Add it at its place with \"template_slide\": 2 and its new content."));
Assert.That(issues, Has.Some.Contains("Template slide 3 is missing. Add it at its place as { \"template_slide\": 3 }."));
});
}
}
[Test]
@@ -163,12 +163,12 @@ public sealed class SlideDeckSpecValidatorTests
]);
var issues = SlideDeckSpecValidator.Validate(deck, TemplateWithSlides(), 0, 0, USES);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(issues, Has.Some.Contains("Slide 1 keeps template slide 1 unchanged, so it takes no content."));
Assert.That(issues, Has.Some.Contains("Slide 2 is to edit template slide 2, but writes no content."));
Assert.That(issues, Has.Some.Contains("Slide 3 keeps template slide 3 unchanged"), "Slide 3 of the template is to be kept, so the edit is sent back.");
});
}
var editedDeck = new SlideDeckSpec("Weekly", [Keep(1), Edit(2, "News", subtitle: "This week"), Keep(3), Edit(4, texts: [Text("Thema:\tWeekly")])]);
Assert.That(SlideDeckSpecValidator.Validate(editedDeck, TemplateWithSlides(), 0, 0, USES), Has.Some.Contains("Slide 2 has a subtitle, but layout L2 has none"), "An edited slide has the slots of its template slide.");
@@ -180,11 +180,11 @@ public sealed class SlideDeckSpecValidatorTests
var unordered = new SlideDeckSpec("Weekly", [Keep(3), Edit(2, "News", texts: [Text("New")]), Keep(1), Edit(4, texts: [Text("Thema:\tWeekly")])]);
var imprintInBetween = new SlideDeckSpec("Weekly", [Keep(1), Edit(2, "News", texts: [Text("New")]), Keep(3), Edit(4, texts: [Text("Thema:\tWeekly")]), Slide("L2", "Thanks")]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.Validate(unordered, TemplateWithSlides(), 0, 0, USES), Has.Some.Contains("The template slides are out of order. Keep them in the order 1, 2, 3, 4"));
Assert.That(SlideDeckSpecValidator.Validate(imprintInBetween, TemplateWithSlides(), 0, 0, USES), Is.Empty, "An imprint is a template slide like any other, so a new slide may follow it.");
});
}
}
[Test]
@@ -193,7 +193,7 @@ public sealed class SlideDeckSpecValidatorTests
var template = TemplateWithSlides();
TemplateSlideUse[] uses = [TemplateSlideUse.KEEP, TemplateSlideUse.EDIT, TemplateSlideUse.OMIT, TemplateSlideUse.KEEP, TemplateSlideUse.KEEP];
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.CanWrite(Slide("L2"), template, uses), Is.True);
Assert.That(SlideDeckSpecValidator.CanWrite(Slide("L9"), template, uses), Is.False);
@@ -201,7 +201,7 @@ public sealed class SlideDeckSpecValidatorTests
Assert.That(SlideDeckSpecValidator.CanWrite(Edit(5, "Thanks"), template, uses), Is.False, "Such a slide has no slots to write content into.");
Assert.That(SlideDeckSpecValidator.CanWrite(Keep(3), template, uses), Is.False, "The user left this slide out.");
Assert.That(SlideDeckSpecValidator.CanWrite(Keep(9), template, uses), Is.False);
});
}
}
[Test]
@@ -218,11 +218,11 @@ public sealed class SlideDeckSpecValidatorTests
var edited = SlideDeckSpecValidator.Validate(new SlideDeckSpec("Mars", [Edit(1, texts: [points])]), template, 0, 0, [TemplateSlideUse.EDIT]);
var written = SlideDeckSpecValidator.Validate(new SlideDeckSpec("Mars", [Slide("L1", texts: [points])]), template, 0, 0, [TemplateSlideUse.OMIT]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(edited, Has.Some.Contains("Slide 1: text 1 needs about 8 lines, but only about 5 fit."), "Four points of two lines each overfill the half the slide leaves.");
Assert.That(written, Is.Empty, "On a slide written anew, the same points fit the whole slot.");
});
}
}
[Test]
@@ -243,7 +243,7 @@ public sealed class SlideDeckSpecValidatorTests
TemplateSlideUse[] uses = [TemplateSlideUse.EDIT, TemplateSlideUse.KEEP];
IReadOnlyList<string> IssuesOf(SlideSpec first, SlideSpec? added = null) => SlideDeckSpecValidator.Validate(new SlideDeckSpec("Weekly", added is null ? [first, Keep(2)] : [first, Keep(2), added]), template, 0, 0, uses);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(IssuesOf(Edit(1) with { Removals = ["element 1", "label 1"] }), Is.Empty, "Removing alone edits a slide.");
Assert.That(IssuesOf(Edit(1) with { Removals = ["element 2"] }), Has.Some.Contains("Slide 1 removes \"element 2\", which template slide 1 does not have."));
@@ -252,7 +252,7 @@ public sealed class SlideDeckSpecValidatorTests
Assert.That(IssuesOf(Edit(1, texts: [Text("New news")]) with { Removals = ["text 1"] }), Has.Some.Contains("Slide 1 fills text 1 and removes it as well."));
Assert.That(IssuesOf(Edit(1) with { Removals = ["element 1"] }, Slide("L1") with { Removals = ["element 1"] }), Has.Some.Contains("Slide 3 is a new slide, which has nothing to remove."));
Assert.That(SlideDeckSpecValidator.Validate(new SlideDeckSpec("Weekly", [Edit(1) with { Removals = ["element 1"] }, Keep(2) with { Removals = ["element 1"] }]), template, 0, 0, uses), Has.Some.Contains("Slide 2 keeps template slide 2 unchanged, so it takes no content."));
});
}
}
[Test]
@@ -263,7 +263,7 @@ public sealed class SlideDeckSpecValidatorTests
var template = PresentationTemplate.Read(file);
IReadOnlyList<string> IssuesOf(SlideSpec slide) => SlideDeckSpecValidator.Validate(new SlideDeckSpec("Missions", [slide]), template, 0, 0, [TemplateSlideUse.EDIT]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(IssuesOf(Edit(1, texts: [Text("Mars"), Text("Moon"), Text("Venus")], labels: ["Near", "Far", "Farther"])), Is.Empty, "Every copy takes content like a slot.");
Assert.That(IssuesOf(Edit(1, texts: [Text("Mars"), Text("Moon"), Text("Venus"), Text("Jupiter")])), Has.Some.Contains("Slide 1 has 4 texts, but layout L1 has only 3."));
@@ -271,7 +271,7 @@ public sealed class SlideDeckSpecValidatorTests
Assert.That(IssuesOf(Edit(1, labels: ["Near", "Far"]) with { Removals = ["label 3", "text 3"] }), Is.Empty);
Assert.That(IssuesOf(Edit(1, labels: ["Near"]) with { Removals = ["label 4"] }), Has.Some.Contains("Slide 1 removes \"label 4\", which template slide 1 does not have."));
Assert.That(IssuesOf(Edit(1, texts: [Text("Mars"), Text("Moon")]) with { Removals = ["text 2"] }), Has.Some.Contains("Slide 1 fills text 2 and removes it as well."));
});
}
}
[Test]
@@ -287,13 +287,13 @@ public sealed class SlideDeckSpecValidatorTests
var deviating = new SlideDeckSpec("Mars", [Slide("L3", "Mars"), Slide("L3", "Why Mars", texts: [Text("Close to Earth")], startsChapter: true)]);
var issues = SlideDeckSpecValidator.Validate(deviating, TEMPLATE, 0, 0, chosenLayouts: chosen);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.Validate(fitting, TEMPLATE, 0, 0, chosenLayouts: chosen), Is.Empty);
Assert.That(issues, Has.Some.Contains("Slide 1 is the title slide, but uses layout L3. The title slide uses layout L1."));
Assert.That(issues, Has.Some.Contains("Slide 2 starts a chapter, but uses layout L3. Chapters start with a slide on layout L2; put their content on the slides that follow."));
Assert.That(SlideDeckSpecValidator.Validate(deviating, TEMPLATE, 0, 0), Is.Empty, "Without choices, the model decides every layout.");
});
}
}
[Test]
@@ -311,13 +311,13 @@ public sealed class SlideDeckSpecValidatorTests
var content = Slide("L3", "Why Mars", texts: [Text("Close to Earth")]);
var missing = SlideDeckSpecValidator.Validate(new SlideDeckSpec("Mars", [title, content]), TEMPLATE, 0, 0, chosenLayouts: chosen);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.Validate(new SlideDeckSpec("Mars", [title, contents, content, imprint]), TEMPLATE, 0, 0, chosenLayouts: chosen), Is.Empty);
Assert.That(missing, Has.Some.Contains("The presentation has no slide on layout L4 for the table of contents, which lists the topics or chapters of the presentation without times. Add one right after the title slide."));
Assert.That(missing, Has.Some.Contains("The presentation has no slide on layout L6 for the imprint"));
Assert.That(SlideDeckSpecValidator.Validate(new SlideDeckSpec("Mars", [title, contents, imprint, content]), TEMPLATE, 0, 0, chosenLayouts: chosen), Has.Some.Contains("The imprint on layout L6 is not the last slide."));
});
}
}
[Test]
@@ -327,12 +327,12 @@ public sealed class SlideDeckSpecValidatorTests
var morePoints = new SlideTextBlock([new SlideParagraph("Has a day of 24.6 hours", 0), new SlideParagraph("Close to the day on Earth", 1)]);
var deck = new SlideDeckSpec("Mars", [Slide("L1", "Mars"), Slide("L4", "Why Mars", texts: [points, morePoints])]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.Validate(deck, TEMPLATE, 0, 0, maxMainPointsPerSlide: 3), Is.Empty, "A sub-point belongs to its main point.");
Assert.That(SlideDeckSpecValidator.Validate(deck, TEMPLATE, 0, 0, maxMainPointsPerSlide: 2), Has.Some.Contains("Slide 2 has 3 main points, but at most 2 per slide were asked for, all its texts together."), "The texts of a slide count together.");
Assert.That(SlideDeckSpecValidator.Validate(deck, TEMPLATE, 0, 0), Is.Empty, "Without a maximum, the model chooses.");
});
}
}
[Test]
@@ -366,11 +366,11 @@ public sealed class SlideDeckSpecValidatorTests
]);
var issues = SlideDeckSpecValidator.Validate(deck, TEMPLATE, 2, 0);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(issues, Has.Some.Contains("Picture 1 is on slides 1, 2. Place it on exactly one of them."));
Assert.That(issues, Has.Some.Contains("Picture 2 is on no slide. Place every picture of PICTURES on exactly one slide with a picture slot."));
});
}
}
[Test]
@@ -397,13 +397,13 @@ public sealed class SlideDeckSpecValidatorTests
]);
var issues = SlideDeckSpecValidator.Validate(deck, TEMPLATE, 1, 0, libraryPictures: library);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(issues, Has.Some.Contains("Picture 2 of PICTURE_LIBRARY is on slides 2, 3. Use a picture of the library at most once; only icons may repeat."));
Assert.That(issues, Has.None.Contains("Picture 3 of PICTURE_LIBRARY"), "An icon may mark several slides.");
Assert.That(issues, Has.None.Contains("refers to picture"), "The numbers of the library follow those of the pictures for the slides.");
Assert.That(issues, Has.None.Contains("is on no slide"), "Only the pictures for the slides have to be used.");
});
}
}
[Test]
@@ -423,12 +423,12 @@ public sealed class SlideDeckSpecValidatorTests
]);
var issues = SlideDeckSpecValidator.Validate(deck, template, 0, 0, libraryPictures: library);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(issues, Has.Some.Contains("Slide 1 puts icon 1 into picture 1, which is a backdrop."));
Assert.That(issues, Has.None.Contains("Slide 2 puts icon"), "A smaller picture slot is the place for an icon.");
Assert.That(issues, Has.None.Contains("Slide 3 puts icon"), "A photo fills a backdrop well.");
});
}
}
[Test]
@@ -437,12 +437,12 @@ public sealed class SlideDeckSpecValidatorTests
var deck = new SlideDeckSpec("Weekly", [Keep(1), Slide("L2", texts: [Text("Close to Earth")]), Edit(4, texts: [Text("Thema:\tWeekly")])]);
var basis = TemplateWithSlides().DescribeBasisOf(deck);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(basis.Keys, Is.EquivalentTo(new[] { "template slide 1", "L2", "template slide 4" }), "An edited template slide fills the slots of its layout, a kept one does not.");
Assert.That(SlideDeckSpecValidator.StillFits(deck, TemplateWithSlides(), basis), Is.True, "The same file, read once more.");
Assert.That(SlideDeckSpecValidator.StillFits(deck, ChangedTemplateWithSlides(newLayoutAt: 3), basis), Is.True, "A new layout at the end of the slide master leaves the IDs of the others alone.");
});
}
}
[Test]
@@ -451,12 +451,12 @@ public sealed class SlideDeckSpecValidatorTests
var deck = new SlideDeckSpec("Weekly", [Keep(1), Slide("L2", texts: [Text("Close to Earth")]), Keep(3)]);
var basis = TemplateWithSlides().DescribeBasisOf(deck);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(SlideDeckSpecValidator.StillFits(deck, ChangedTemplateWithSlides(newLayoutAt: 1), basis), Is.False, "L2 is another layout now.");
Assert.That(SlideDeckSpecValidator.StillFits(deck, ChangedTemplateWithSlides(deletedSlide: 3), basis), Is.False, "Template slide 3 is the imprint now.");
Assert.That(SlideDeckSpecValidator.StillFits(deck, ChangedTemplateWithSlides(deletedSlide: 3), new Dictionary<string, string>()), Is.False, "Without a basis, nothing is known to fit.");
});
}
}
/// <summary>
@@ -38,12 +38,12 @@ public sealed class StreamedSlideCounterTests
public void AKeyCountsOnlyOnceItIsComplete()
{
var counter = new StreamedSlideCounter();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(counter.Count("""{"slides": [{"lay"""), Is.Zero);
Assert.That(counter.Count("""{"slides": [{"layout"""), Is.Zero, "The closing quotation mark is still missing.");
Assert.That(counter.Count("""{"slides": [{"layout": "L1"}"""), Is.EqualTo(1));
});
}
}
[Test]
@@ -23,11 +23,11 @@ public sealed class TemplateSlideUsesTests
var template = TemplateWithAContentSlideAndAClosingSlide();
var uses = new Dictionary<int, TemplateSlideUse> { [1] = TemplateSlideUse.EDIT, [2] = TemplateSlideUse.EDIT }.ToTemplateSlideUses(template);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.Slides.Select(slide => slide.IsEditable), Is.EqualTo(new[] { true, false }), "The test needs one slide which the model can edit and one which it cannot.");
Assert.That(uses, Is.EqualTo(new[] { TemplateSlideUse.EDIT, TemplateSlideUse.KEEP }), "An organization may lock the choice, so it must not block the form.");
});
}
}
[Test]