Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions OfficeIMO.Tests/Visio.ConnectionPoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,130 @@ public void SparseConnectionPointIndicesDoNotFallbackToSequentialGlueWhenReferen
Assert.Same(loaded.Pages[0].Shapes[1].ConnectionPoints[0], loadedConnector.ToConnectionPoint);
}

[Fact]
public void UnresolvedConnectionCellReferencesArePreservedOnRoundTrip() {
string filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx");

VisioDocument document = VisioDocument.Create(filePath);
VisioPage page = document.AddPage("Page-1");

VisioShape from = new("1", 2, 2, 2, 2, "From");
page.Shapes.Add(from);

VisioShape to = new("2", 6, 2, 2, 2, "To");
page.Shapes.Add(to);

page.Connectors.Add(new VisioConnector(from, to));
document.Save();

RewritePage(filePath, pageXml => {
XNamespace ns = "http://schemas.microsoft.com/office/visio/2012/main";

foreach (XElement connect in pageXml.Root!.Element(ns + "Connects")!.Elements(ns + "Connect")) {
if ((string?)connect.Attribute("FromCell") == "BeginX") {
connect.SetAttributeValue("ToCell", "LocPinX");
} else if ((string?)connect.Attribute("FromCell") == "EndX") {
connect.SetAttributeValue("ToCell", "Width");
}
}
});

VisioDocument loaded = VisioDocument.Load(filePath);
VisioConnector loadedConnector = Assert.Single(loaded.Pages[0].Connectors);
Assert.Null(loadedConnector.FromConnectionPoint);
Assert.Null(loadedConnector.ToConnectionPoint);

loaded.Save();

XNamespace verifyNs = "http://schemas.microsoft.com/office/visio/2012/main";
XElement[] connectRows = ReadPage(filePath).Root!.Element(verifyNs + "Connects")!.Elements(verifyNs + "Connect").ToArray();
Assert.Equal("LocPinX", connectRows.First(e => (string?)e.Attribute("FromCell") == "BeginX").Attribute("ToCell")?.Value);
Assert.Equal("Width", connectRows.First(e => (string?)e.Attribute("FromCell") == "EndX").Attribute("ToCell")?.Value);
}

[Fact]
public void ExtraConnectAttributesArePreservedOnRoundTrip() {
string filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx");

VisioDocument document = VisioDocument.Create(filePath);
VisioPage page = document.AddPage("Page-1");

VisioShape from = new("1", 2, 2, 2, 2, "From");
VisioShape to = new("2", 6, 2, 2, 2, "To");
page.Shapes.Add(from);
page.Shapes.Add(to);
page.Connectors.Add(new VisioConnector(from, to));
document.Save();

RewritePage(filePath, pageXml => {
XNamespace ns = "http://schemas.microsoft.com/office/visio/2012/main";

foreach (XElement connect in pageXml.Root!.Element(ns + "Connects")!.Elements(ns + "Connect")) {
if ((string?)connect.Attribute("FromCell") == "BeginX") {
connect.SetAttributeValue("ToPart", "9");
connect.SetAttributeValue("Del", "1");
} else if ((string?)connect.Attribute("FromCell") == "EndX") {
connect.SetAttributeValue("ToPart", "12");
}
}
});

VisioDocument loaded = VisioDocument.Load(filePath);
loaded.Save();

XNamespace verifyNs = "http://schemas.microsoft.com/office/visio/2012/main";
XElement[] connectRows = ReadPage(filePath).Root!.Element(verifyNs + "Connects")!.Elements(verifyNs + "Connect").ToArray();
XElement begin = connectRows.First(e => (string?)e.Attribute("FromCell") == "BeginX");
XElement end = connectRows.First(e => (string?)e.Attribute("FromCell") == "EndX");
Assert.Equal("9", begin.Attribute("ToPart")?.Value);
Assert.Equal("1", begin.Attribute("Del")?.Value);
Assert.Equal("12", end.Attribute("ToPart")?.Value);
}

[Fact]
public void ReconnectClearsPreservedAttributesOnlyForUpdatedEndpoint() {
string filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx");

VisioDocument document = VisioDocument.Create(filePath);
VisioPage page = document.AddPage("Page-1");

VisioShape from = new("1", 2, 2, 2, 2, "From");
VisioShape replacement = new("2", 4, 2, 2, 2, "Replacement");
VisioShape to = new("3", 7, 2, 2, 2, "To");
page.Shapes.Add(from);
page.Shapes.Add(replacement);
page.Shapes.Add(to);
page.Connectors.Add(new VisioConnector(from, to));
document.Save();

RewritePage(filePath, pageXml => {
XNamespace ns = "http://schemas.microsoft.com/office/visio/2012/main";

foreach (XElement connect in pageXml.Root!.Element(ns + "Connects")!.Elements(ns + "Connect")) {
if ((string?)connect.Attribute("FromCell") == "BeginX") {
connect.SetAttributeValue("ToPart", "9");
} else if ((string?)connect.Attribute("FromCell") == "EndX") {
connect.SetAttributeValue("ToPart", "12");
}
}
});

VisioDocument loaded = VisioDocument.Load(filePath);
VisioPage loadedPage = loaded.Pages[0];
VisioConnector connector = Assert.Single(loadedPage.Connectors);

loadedPage.ReconnectConnectorStart(connector, loadedPage.Shapes[1], VisioSide.Left);
loaded.Save();

XNamespace verifyNs = "http://schemas.microsoft.com/office/visio/2012/main";
XElement[] connectRows = ReadPage(filePath).Root!.Element(verifyNs + "Connects")!.Elements(verifyNs + "Connect").ToArray();
XElement begin = connectRows.First(e => (string?)e.Attribute("FromCell") == "BeginX");
XElement end = connectRows.First(e => (string?)e.Attribute("FromCell") == "EndX");
Assert.Null(begin.Attribute("ToPart"));
Assert.Equal("Connections.X1", begin.Attribute("ToCell")?.Value);
Assert.Equal("12", end.Attribute("ToPart")?.Value);
}

private static XDocument ReadPage(string vsdxPath) {
using FileStream stream = File.Open(vsdxPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using ZipArchive archive = new(stream, ZipArchiveMode.Read);
Expand Down
56 changes: 56 additions & 0 deletions OfficeIMO.Tests/Visio.DocumentValidation.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using OfficeIMO.Visio;
using Xunit;

Expand Down Expand Up @@ -64,5 +65,60 @@ public void ValidateReportsNegativeDimensionsAndDetachedConnectionPoints() {
Assert.Contains(issues, issue => issue.Contains("cannot have a negative height"));
Assert.Contains(issues, issue => issue.Contains("source connection point"));
}

[Fact]
public void SaveRejectsInvalidDocumentsWithValidationSummary() {
string filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx");
VisioDocument document = VisioDocument.Create(filePath);
VisioPage first = document.AddPage("Page-1", id: 0);
VisioPage second = document.AddPage("Page-2", id: 0);

VisioShape shared = new("dup", 1, 1, 1, 1, "Shared");
first.Shapes.Add(shared);
second.Shapes.Add(new VisioShape("outside", 2, 2, 1, 1, "Outside"));
first.Connectors.Add(new VisioConnector("dup", shared, second.Shapes[0]));

InvalidOperationException exception = Assert.Throws<InvalidOperationException>(() => document.Save());

Assert.Contains("validation failed", exception.Message);
Assert.Contains("Duplicate page id '0'", exception.Message);
Assert.Contains("references a target shape that is not part of the page", exception.Message);
}

[Fact]
public void ValidateReportsNegativePageIds() {
VisioDocument document = VisioDocument.Create(Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx"));
VisioPage page = new("Detached") { Id = -1 };

object? rawPages = typeof(VisioDocument)
.GetField("_pages", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!
.GetValue(document);
var pagesList = Assert.IsType<System.Collections.Generic.List<VisioPage>>(rawPages);
pagesList.Add(page);

string[] issues = document.Validate().ToArray();

Assert.Contains(issues, issue => issue.Contains("must have a non-negative id"));
}

[Fact]
public void ValidateReportsCyclicShapeHierarchyWithoutRecursingForever() {
VisioDocument document = VisioDocument.Create(Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx"));
VisioPage page = document.AddPage("Page-1");
VisioShape group = new("1") { Type = "Group" };
VisioShape child = new("2", 1, 1, 1, 1, "Child");
group.Children.Add(child);
page.Shapes.Add(group);

List<VisioShape> rawChildren = Assert.IsType<List<VisioShape>>(typeof(VisioShape)
.GetField("_children", BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(child));
rawChildren.Add(group);

string[] issues = document.Validate().ToArray();

Assert.Contains(issues, issue => issue.Contains("cyclic parent/child hierarchy"));
Assert.Contains(issues, issue => issue.Contains("inconsistent parent reference"));
}
}
}
20 changes: 20 additions & 0 deletions OfficeIMO.Tests/Visio.Fluent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,26 @@ public void FluentConnectCanTargetSidesAndStyleLines() {
Assert.Equal(EndArrow.Triangle, connector.EndArrow);
}

[Fact]
public void FluentConnectUsesPageScopedConnectorIdsAcrossDocuments() {
static VisioDocument CreateDocument(string filePath) {
VisioDocument document = VisioDocument.Create(filePath);
document.AsFluent()
.Page("Page1", p => p
.Rect("1", 1, 1, 2, 1, "Left")
.Rect("2", 5, 1, 2, 1, "Right")
.Connect("1", "2"))
.End();
return document;
}

VisioDocument first = CreateDocument(Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx"));
VisioDocument second = CreateDocument(Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx"));

Assert.Equal("3", Assert.Single(first.Pages[0].Connectors).Id);
Assert.Equal("3", Assert.Single(second.Pages[0].Connectors).Id);
}

[Fact]
public void SideSelectionUsesNamedSidePointEvenWhenCustomPointsExist() {
string filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vsdx");
Expand Down
Loading
Loading