using System.Text.Json.Nodes; using OpenNest.Geometry; using static OpenNest.IO.Tests.NestArchiveJson; namespace OpenNest.IO.Tests; /// /// A plate's survives the .nest round trip under an optional /// outline key; files without it load rectangles unchanged, an empty outlined plate is /// kept, and a malformed value loads as a rectangle with a reader warning. /// public class SheetOutlinePersistenceTests { /// A 96 x 48 sheet missing its upper-right 48 x 24 corner, with a burned 4 x 4 patch. private static SheetOutline Ell() => new( new[] { new Vector(0, 0), new Vector(96, 0), new Vector(96, 24), new Vector(48, 24), new Vector(48, 48), new Vector(0, 48) }, new[] { new[] { new Vector(10, 10), new Vector(14, 10), new Vector(14, 14), new Vector(10, 14) } }); private static Nest OutlinedNest() { var nest = new Nest("outlined"); nest.Plates.Add(new Plate { Outline = Ell(), Quadrant = 3, EdgeSpacing = new Spacing(1, 2, 3, 4) }); return nest; } [Fact] public void OutlineRoundTripsWithObstaclesAndKeepsAnEmptyPlate() { var (reopened, warnings) = RoundTrip(ArchiveBytes(OutlinedNest())); Assert.Empty(warnings); var plate = Assert.Single(reopened.Plates); Assert.Equal(Ell(), plate.Outline); Assert.Equal(48, plate.Size.Width); Assert.Equal(96, plate.Size.Length); Assert.Equal(3, plate.Quadrant); Assert.Equal(96 * 48 - 48 * 24 - 16, plate.Area(), 6); } [Fact] public void FileShapeIsPointsAndObstaclesInPlateCoordinates() { var plates = NestJson(ArchiveBytes(OutlinedNest()))["plates"]!.AsArray(); var outline = Assert.Single(plates)!["outline"]!.AsObject(); var points = outline["points"]!.AsArray(); Assert.Equal(6, points.Count); Assert.Equal(0, (double)points[0]!["x"]!); Assert.Equal(0, (double)points[0]!["y"]!); Assert.Equal(96, (double)points[1]!["x"]!); var obstacle = Assert.Single(outline["obstacles"]!.AsArray())!.AsArray(); Assert.Equal(4, obstacle.Count); Assert.Equal(10, (double)obstacle[0]!["x"]!); } [Fact] public void RectangularPlatesWriteNoOutlineKeyAndLoadWithout() { var nest = new Nest("plain"); nest.Plates.Add(new Plate(48, 96)); nest.Plates[0].CutOffs.Add(new CutOff(new Vector(40, 0), CutOffAxis.Vertical)); var archive = ArchiveBytes(nest); var json = NestJson(archive); Assert.Null(Assert.Single(json["plates"]!.AsArray())!["outline"]); var (reopened, warnings) = RoundTrip(archive); Assert.Empty(warnings); Assert.Null(Assert.Single(reopened.Plates).Outline); } [Theory] [InlineData("\"not an object\"", "not an object")] [InlineData("{}", "'points' is missing")] [InlineData("{\"points\": 5}", "not an array")] [InlineData("{\"points\": [{\"x\": 0, \"y\": 0}, {\"x\": \"ten\", \"y\": 0}, {\"x\": 0, \"y\": 10}]}", "vertex 2")] [InlineData("{\"points\": [{\"x\": 0, \"y\": 0}, {\"x\": 10, \"y\": 0}]}", "three distinct vertices")] [InlineData("{\"points\": [{\"x\": 0, \"y\": 0}, {\"x\": 10, \"y\": 10}, {\"x\": 10, \"y\": 0}, {\"x\": 0, \"y\": 10}]}", "no area")] [InlineData("{\"points\": [{\"x\": 0, \"y\": 0}, {\"x\": 10, \"y\": 0}, {\"x\": 10, \"y\": 10}, {\"x\": 2, \"y\": -2}]}", "simple polygon")] [InlineData("{\"points\": [{\"x\": 0, \"y\": 0}, {\"x\": 10, \"y\": 0}, {\"x\": 10, \"y\": 10}], \"obstacles\": 3}", "'obstacles' is not an array")] public void MalformedOutlineLoadsARectangleWithAWarning(string value, string problem) { var archive = ArchiveBytes(OutlinedNest()); var json = NestJson(archive); var plate = Assert.Single(json["plates"]!.AsArray())!.AsObject(); plate["outline"] = JsonNode.Parse(value); var (reopened, warnings) = RoundTrip(ReplaceNestJson(archive, json)); var loaded = Assert.Single(reopened.Plates); Assert.Null(loaded.Outline); Assert.Equal(48, loaded.Size.Width); Assert.Equal(96, loaded.Size.Length); var warning = Assert.Single(warnings); Assert.Contains("Plate 1", warning); Assert.Contains(problem, warning); } [Fact] public void SnapshotRestoresTheOutline() { var nest = OutlinedNest(); var snapshot = NestSnapshot.Capture(nest); nest.Plates[0].Outline = null; nest.Plates[0].Size = new Size(60, 120); var restored = snapshot.Restore(nest); Assert.Equal(Ell(), Assert.Single(restored.Plates).Outline); Assert.Equal(96, restored.Plates[0].Size.Length); } }