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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ The full suite exercises real Visio COM calls with no mock/fake layer. CI runs o
- **Releases:** NuGet `VisioAutomation2010` **3.1.0** (tag `VisioAutomation_3.1.0`) and Visio PowerShell module **4.7.3** (tag `VisioPS_4.7.3`) shipped on 2026-09-30. The Release-build change for the NuGet flow ([#181](https://github.com/saveenr/VisioAutomation/issues/181)) is done and closed.
- **Repo:** `master` at `92dec356` (PRs up to #227 merged), no open PRs, working trees clean. The older `claude/*` and `experiment/linq-shapesheet` branches are leftovers from earlier sessions.
- **Unreleased changes** (`NuGet/CHANGELOG.md` `[Unreleased]`): the first tier of [#225](https://github.com/saveenr/VisioAutomation/issues/225) is Added (more directed graph XML options: `<documentoptions>`, shape `width`/`height`, `<hyperlink>`, `<cells>`, typed `<customprop>`, and per-connector `connectortype`, `<cells>` and `<customprop>`); the rest is Fixed: `DrawDataTable` honors widths and heights, `DrawDataTableModel` draws on the target page, `DrawXmlModel` roots the tree at the document element and now has an undo scope, `DrawDirectedGraphDocument` honors its `DirectedGraphStyling`, and `DrawOrgChart` no longer resizes the page you pass it. The next version is not decided; these are fixes only, so a patch release is the likely fit.
- **Tests:** 282 tests across four projects (VTest 108, VTest.Models 104, VTest.Scripting 43, VTest.PowerShell 27). The last full Release run (256) was on 2026-09-30; the newest tests are the regression tests for [#197](https://github.com/saveenr/VisioAutomation/issues/197), [#222](https://github.com/saveenr/VisioAutomation/issues/222) and [#219](https://github.com/saveenr/VisioAutomation/issues/219), and the 23 tests in `DirectedGraphXmlOptionsTests` for [#225](https://github.com/saveenr/VisioAutomation/issues/225).
- **Tests:** 328 tests across four projects (VTest 108, VTest.Models 104, VTest.Scripting 43, VTest.PowerShell 73). The last full Release run (256) was on 2026-09-30; the newest tests are the regression tests for [#197](https://github.com/saveenr/VisioAutomation/issues/197), [#222](https://github.com/saveenr/VisioAutomation/issues/222) and [#219](https://github.com/saveenr/VisioAutomation/issues/219), the 23 tests in `DirectedGraphXmlOptionsTests` for [#225](https://github.com/saveenr/VisioAutomation/issues/225), and the 46 cmdlet-binding tests of the second slice of [#173](https://github.com/saveenr/VisioAutomation/issues/173).
- **Docs:** the .NET GitBook's Diagram models section was reorganized on 2026-10-01 (docs repo PRs 8 and 9, live). Every model page now has a "Where the output goes" section; the DOM page has a render performance page beneath it. The 2026-09 entries on the docs-updates page record each change. The "Models docs follow-ups" entry in [`docs/futures/docs.md`](docs/futures/docs.md) lists which doc statements to revisit when each source issue below is decided, and the release-gated "unreleased" notes to turn into version statements at the next release. The [#219](https://github.com/saveenr/VisioAutomation/issues/219) fix and the new directed graph XML options ([#225](https://github.com/saveenr/VisioAutomation/issues/225)) are described in the docs as unreleased changes after 3.1.0; the fixes for [#197](https://github.com/saveenr/VisioAutomation/issues/197) and [#222](https://github.com/saveenr/VisioAutomation/issues/222) are not yet described.
- **Docs conventions:** no em-dashes in either GitBook. The docs repos publish when a PR merges to `main`, and each PR gets a GitBook preview. Cite the issue number on any page that describes behavior an open issue may change.
- **Docs viewer:** the local GitBook viewer now lives in its own private repo, `TheSevenPens/LocalGitbookviewer` (cloned at `C:/Users/savee/Documents/GitHub/LocalGitbookviewer`). Run `view.cmd <docs folder>` from that checkout. Its backlog is in that repo's `BACKLOG.md`.
Expand Down Expand Up @@ -188,7 +188,7 @@ Roughly in the order worth doing. Pick from the top. ([#219](https://github.com/
1. **Decide the three public types with no real caller, together:** [#218](https://github.com/saveenr/VisioAutomation/issues/218) Box layout, [#220](https://github.com/saveenr/VisioAutomation/issues/220) form page model, [#221](https://github.com/saveenr/VisioAutomation/issues/221) `ContainerLayout` (draws plain rectangles; the `ContainerMaster` option is never read). The options are in each issue; the docs side of each is listed in the "Models docs follow-ups" backlog entry.
2. **Cut the next release when ready.** Versions are not decided (fixes only, so likely 3.1.1 and a PS module patch). The flow is in [`docs/futures/releases.md`](docs/futures/releases.md). At release time also turn the "unreleased" notes in both GitBooks into version statements and add the new versions to both version-compatibility pages.
3. **Design discussions, not yet quick:** [#223](https://github.com/saveenr/VisioAutomation/issues/223) `client.Model` draw methods are inconsistent, [#225](https://github.com/saveenr/VisioAutomation/issues/225) the rest of the directed graph XML options (the first tier shipped in the unreleased changes; per-edge master and stencil, renderer and styling from XML, and an XSD remain), [#229](https://github.com/saveenr/VisioAutomation/issues/229) `NewDocumentFromTemplate` does not base the document on the template (affects `New-VisioDocument -Template`), and [#198](https://github.com/saveenr/VisioAutomation/issues/198) / [#199](https://github.com/saveenr/VisioAutomation/issues/199) drawing a single directed graph onto the current page.
4. **Earlier backlog:** [#191](https://github.com/saveenr/VisioAutomation/issues/191) migrate cmdlets to `ThrowTerminatingError` (makes `InvokeScriptStrict` redundant), [#190](https://github.com/saveenr/VisioAutomation/issues/190) the `VTest.PowerShell` two-Client mismatch, [#183](https://github.com/saveenr/VisioAutomation/issues/183) VisioScripting dead-method removal (CY27Q1; re-verify zero callers first), [#173](https://github.com/saveenr/VisioAutomation/issues/173) more cmdlet-binding tests (about 50 cmdlets still untested), [#163](https://github.com/saveenr/VisioAutomation/issues/163) and [#164](https://github.com/saveenr/VisioAutomation/issues/164) (the `PSCmdlet` question; see the memory note on why cmdlets derive from `Cmdlet`).
4. **Earlier backlog:** [#191](https://github.com/saveenr/VisioAutomation/issues/191) migrate cmdlets to `ThrowTerminatingError` (makes `InvokeScriptStrict` redundant), [#190](https://github.com/saveenr/VisioAutomation/issues/190) the `VTest.PowerShell` two-Client mismatch, [#183](https://github.com/saveenr/VisioAutomation/issues/183) VisioScripting dead-method removal (CY27Q1; re-verify zero callers first), [#173](https://github.com/saveenr/VisioAutomation/issues/173) more cmdlet-binding tests (the second slice covered 13 cmdlets; roughly 40 are still untested, see `docs/futures/test-coverage-gaps.md`), [#232](https://github.com/saveenr/VisioAutomation/issues/232) `Get-VisioPage -ID` uses the page index, not the page ID, [#163](https://github.com/saveenr/VisioAutomation/issues/163) and [#164](https://github.com/saveenr/VisioAutomation/issues/164) (the `PSCmdlet` question; see the memory note on why cmdlets derive from `Cmdlet`).

**Calendar-bound**

Expand Down
2 changes: 2 additions & 0 deletions NuGet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ The format follows [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/
- On `<connector>`: a per-edge `connectortype` that overrides the page setting, a `<cells>` child (explicit cells win over the `color`, `weight` and arrow defaults), and `<customprop>` children.

### Fixed
- `Client.Page.SetFormatCells` now writes the page format cells to the page sheet. Previously it committed them to the `Page` object, which always threw a `COMException`, so `Format-VisioPage -Width` / `-Height` could not work. Found by the cmdlet-binding tests ([#173](https://github.com/saveenr/VisioAutomation/issues/173)).
- `HyperlinkHelper.Add` (and so `Client.Hyperlink.AddHyperlink`) now applies the `NewWindow`, `Default`, `Invisible` and `SortKey` cells of the `HyperlinkCells` it is given. Previously it ignored them, so the cells always stayed `FALSE` (or unset).
- `Client.Model.DrawOrgChart`, and so `Out-VisioApplication -OrgChart`, no longer resizes the page passed as its `TargetPage`. The chart is drawn in a new document, and each of its pages is already sized by the render, but the call also resized the caller's own page to fit its contents, which shrank that page for no reason. The `TargetPage` now only supplies the application. Closes [#219](https://github.com/saveenr/VisioAutomation/issues/219).
- Custom properties set on a directed graph connector (`Edge.CustomProperties`) are now applied to the drawn connector, for both dynamic and Bezier connectors. Previously the renderer ignored them.
- `Client.Model.DrawDirectedGraphDocument` now honors its `DirectedGraphStyling` parameter. Previously the argument was accepted and ignored, so every page was drawn with the default styling (the `Dynamic Connector` master from `connec_u.vss`). A null styling still means the default. Closes [#197](https://github.com/saveenr/VisioAutomation/issues/197).
Expand Down
53 changes: 53 additions & 0 deletions VisioAutomation_2010/VTest.PowerShell/CmdletScriptExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using MUT = Microsoft.VisualStudio.TestTools.UnitTesting;

namespace VTest.PowerShell
{
// Helpers for cmdlet-binding tests that run whole scripts inside the session's runspace, so the
// cmdlets go through PowerShell's parameter binder (see CmdletBindingTests for why that matters).
// Everything a test needs is created inside the script and plain values are returned, which
// keeps COM objects from crossing between the script and the test.
public static class CmdletScriptExtensions
{
// Runs "body" in a fresh Visio document and returns what it writes to the pipeline.
// The document is closed afterward without a save prompt.
public static List<T> RunInNewDocument<T>(this VisioPSSession session, string body)
{
string script =
"$doc = New-VisioDocument; " +
"try { " + body + " } " +
"finally { try { $doc.Saved = $true; $doc.Close() } catch { } }";
return session.InvokeScriptStrict<T>(script);
}

// Runs "body" in a fresh Visio document where the script is expected to fail. Returns the
// exception, which is flattened into its message chain by MessageOf. Fails the test if it succeeds.
public static System.Exception ExpectFailureInNewDocument(this VisioPSSession session, string body)
{
try
{
session.RunInNewDocument<object>(body);
}
catch (System.Exception ex)
{
return ex;
}

MUT.Assert.Fail("Expected the script to fail: " + body);
return null;
}

// The messages of an exception and everything inside it, so a test can look for a phrase
// without caring how many layers PowerShell wrapped the error in.
public static string MessageOf(System.Exception ex)
{
var parts = new List<string>();
while (ex != null)
{
parts.Add(ex.GetType().Name + ": " + ex.Message);
ex = ex.InnerException;
}
return string.Join(" | ", parts);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System.Linq;
using VisioPowerShell.Commands.VisioApplication;
using MUT = Microsoft.VisualStudio.TestTools.UnitTesting;

namespace VTest.PowerShell
{
// Second slice of #173: parameter binding for Get-VisioDocument, Format-VisioWindow and the
// pipeline-driven parameter sets of Out-VisioApplication.
[MUT.TestClass]
public class DocumentWindowCmdletBindingTests
{
private static readonly VisioPSSession Session = new VisioPSSession();

[MUT.ClassInitialize]
public static void ClassInitialize(MUT.TestContext context)
{
var new_visio_application = new NewVisioApplication();

// Out-VisioApplication's input types are created with New-Object in the scripts below, which only
// finds assemblies that are already loaded.
var models_assembly = typeof(VisioAutomation.Models.Data.DataTableModel).Assembly;
}

[MUT.ClassCleanup]
public static void ClassCleanup()
{
try { DocumentWindowCmdletBindingTests.Session.Cmd_Close_VisioApplication(true); }
catch (System.Exception) { }
DocumentWindowCmdletBindingTests.Session.CleanUp();
}

// -- Get-VisioDocument: parameter sets -----------------------------------------

[MUT.TestMethod]
public void GetVisioDocument_ActiveDocument_ReturnsTheActiveDocument()
{
var same = DocumentWindowCmdletBindingTests.Session.RunInNewDocument<bool>(
"(Get-VisioDocument -ActiveDocument).Name -eq $doc.Name");
MUT.Assert.IsTrue(same.Single());
}

[MUT.TestMethod]
public void GetVisioDocument_NoArguments_IncludesTheNewDocument()
{
var found = DocumentWindowCmdletBindingTests.Session.RunInNewDocument<bool>(
"(Get-VisioDocument | ForEach-Object { $_.Name }) -contains $doc.Name");
MUT.Assert.IsTrue(found.Single());
}

[MUT.TestMethod]
public void GetVisioDocument_PositionalName_FiltersByName()
{
var names = DocumentWindowCmdletBindingTests.Session.RunInNewDocument<string>(
"(Get-VisioDocument $doc.Name).Name");
MUT.Assert.AreEqual(1, names.Count);
}

[MUT.TestMethod]
public void GetVisioDocument_ActiveDocumentWithName_FailsToResolveAParameterSet()
{
var ex = DocumentWindowCmdletBindingTests.Session.ExpectFailureInNewDocument("Get-VisioDocument -ActiveDocument -Name 'x'");
MUT.StringAssert.Contains(CmdletScriptExtensions.MessageOf(ex), "Parameter set cannot be resolved");
}

// -- Format-VisioWindow: three parameter sets ----------------------------------

[MUT.TestMethod]
public void FormatVisioWindow_Zoom_SetsTheWindowZoom()
{
var zoom = DocumentWindowCmdletBindingTests.Session.RunInNewDocument<double>(
"Format-VisioWindow -Zoom 0.5; (Get-VisioApplication).ActiveWindow.Zoom");
MUT.Assert.AreEqual(0.5, zoom.Single(), 1e-9);
}

[MUT.TestMethod]
public void FormatVisioWindow_ZoomAndZoomTo_FailToResolveAParameterSet()
{
var ex = DocumentWindowCmdletBindingTests.Session.ExpectFailureInNewDocument("Format-VisioWindow -Zoom 1 -ZoomTo Page");
MUT.StringAssert.Contains(CmdletScriptExtensions.MessageOf(ex), "Parameter set cannot be resolved");
}

[MUT.TestMethod]
public void FormatVisioWindow_UnknownZoomTo_FailsToBind()
{
var ex = DocumentWindowCmdletBindingTests.Session.ExpectFailureInNewDocument("Format-VisioWindow -ZoomTo Bogus");
MUT.StringAssert.Contains(CmdletScriptExtensions.MessageOf(ex), "Cannot bind parameter 'ZoomTo'");
}

// -- Out-VisioApplication: the parameter set is chosen by the type of the piped object ----

[MUT.TestMethod]
public void OutVisioApplication_PipedDataTableModel_DrawsTheTable()
{
var counts = DocumentWindowCmdletBindingTests.Session.RunInNewDocument<int>(
"$dt = New-Object System.Data.DataTable; $null = $dt.Columns.Add('A'); $null = $dt.Columns.Add('B'); $null = $dt.Rows.Add('1', '2'); " +
"$m = New-Object VisioAutomation.Models.Data.DataTableModel; $m.DataTable = $dt; " +
"$m | Out-VisioApplication; (Get-VisioShape | Measure-Object).Count");
MUT.Assert.AreEqual(2, counts.Single(), "one row of two columns is two cell shapes");
}

[MUT.TestMethod]
public void OutVisioApplication_PipedXmlModel_DrawsTheTree()
{
var counts = DocumentWindowCmdletBindingTests.Session.RunInNewDocument<int>(
"$m = New-Object VisioAutomation.Models.Data.XmlModel; $m.XmlDocument = [xml]'<root><a/><b/></root>'; " +
"$m | Out-VisioApplication; (Get-VisioShape | Measure-Object).Count");
MUT.Assert.AreEqual(5, counts.Single(), "three element nodes and two connectors");
}

[MUT.TestMethod]
public void OutVisioApplication_PipedStringIsNotAModel_FailsToBind()
{
var ex = DocumentWindowCmdletBindingTests.Session.ExpectFailureInNewDocument("'not a model' | Out-VisioApplication");
string message = CmdletScriptExtensions.MessageOf(ex);
MUT.Assert.IsTrue(
message.Contains("Parameter set cannot be resolved") || message.Contains("cannot be bound"),
"expected a binding error, got: " + message);
}
}
}
Loading
Loading