Files
OpenNest/OpenNest.Tests/PostSettings/PostSettingsLayoutTests.cs
T
aj c219ec4e66 feat(posts): CI Fiber one program per sheet, configurable pallet change
Multi-sheet nests previously went into one program with a single header
size and one M50 at the end, so sheet 2 would cut into sheet 1's
skeleton. New Sheets settings:

- One program per sheet (default on): JOB.cnc -> JOB-1.cnc, JOB-2.cnc,
  each a full program with its own size and pallet change (CL-series
  batch rule, EM-423 7.4). Single-sheet nests keep the chosen name.
- Off: one program, with /L "L0" + pallet change between sheets;
  mixed sheet sizes are rejected.
- Pallet change code (default M50, unconfirmed for multi-sheet CI Fiber
  runs; documented as a release blocker).

All sheets are validated and rendered before any file is written.
IMultiFilePostProcessor lets the desktop app confirm overwrites of every
target file and list what was saved, and the console print each file.
2026-09-28 18:22:36 -04:00

165 lines
5.1 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using OpenNest.Posts.CincinnatiCIFiber;
using OpenNest.PostSettings;
using Xunit;
namespace OpenNest.Tests.PostSettings;
public class PostSettingsLayoutTests
{
[Fact]
public void CIFiberConfig_BuildsOrderedSections_CoveringEveryEditableProperty()
{
var sections = PostSettingsLayout.TryBuild(typeof(CIFiberPostConfig));
Assert.NotNull(sections);
Assert.Equal(
new[] { "Machine", "Sheets", "Material", "Program output", "Macros" },
sections.Select(s => s.Name)
);
Assert.All(sections, s => Assert.NotEmpty(s.Description));
var shown = sections.SelectMany(s => s.Fields).Select(f => f.Name).ToList();
var editable = typeof(CIFiberPostConfig)
.GetProperties()
.Where(p => p.GetSetMethod() != null)
.Select(p => p.Name)
.ToList();
Assert.Equal(editable.OrderBy(n => n), shown.OrderBy(n => n));
Assert.Equal(shown.Count, shown.Distinct().Count());
var machine = sections[0].Fields;
Assert.Equal(
new[] { "ConfigurationName", "MaxTableX", "MaxTableY" },
machine.Select(f => f.Name)
);
Assert.Equal(PostSettingKind.Decimal, machine[1].Kind);
Assert.Equal("Maximum table X", machine[1].Label);
Assert.Contains("0 disables", machine[1].Description);
var sheets = sections[1].Fields;
Assert.Equal(new[] { "OneProgramPerSheet", "PalletChangeCode" }, sheets.Select(f => f.Name));
Assert.Equal(PostSettingKind.Boolean, sheets[0].Kind);
var materials = sections[2].Fields[0];
Assert.Equal(PostSettingKind.StringMap, materials.Kind);
Assert.Equal("Material name", materials.KeyHeader);
Assert.Equal("Machine code", materials.ValueHeader);
Assert.All(
sections.SelectMany(s => s.Fields),
f => Assert.False(string.IsNullOrWhiteSpace(f.Description), f.Name)
);
}
[Fact]
public void FieldsRoundTripValuesThroughTheConfig()
{
var config = new CIFiberPostConfig();
var fields = PostSettingsLayout
.TryBuild(typeof(CIFiberPostConfig))
.SelectMany(s => s.Fields)
.ToDictionary(f => f.Name);
fields["MaxTableX"].SetValue(config, 120.5);
fields["PostedAccuracy"].SetValue(config, 4);
fields["SkipScribe"].SetValue(config, false);
Assert.Equal(120.5, config.MaxTableX);
Assert.Equal(4, config.PostedAccuracy);
Assert.False(config.SkipScribe);
Assert.Equal(0, fields["PostedAccuracy"].DecimalPlaces);
Assert.Equal(6, fields["PostedAccuracy"].Maximum);
}
[Fact]
public void UnannotatedConfig_FallsBackToPropertyGrid()
{
Assert.Null(PostSettingsLayout.TryBuild(typeof(PlainConfig)));
}
[Fact]
public void UnsupportedPropertyType_FallsBackToPropertyGrid()
{
Assert.Null(PostSettingsLayout.TryBuild(typeof(NestedConfig)));
}
[Fact]
public void UnmarkedEditableProperty_LandsInOtherSection()
{
var sections = PostSettingsLayout.TryBuild(typeof(PartlyMarkedConfig));
Assert.Equal(new[] { "Main", PostSettingsLayout.OtherSection }, sections.Select(s => s.Name));
Assert.Equal("Extra", Assert.Single(sections[1].Fields).Name);
Assert.Equal(PostSettingKind.Choice, sections[1].Fields[0].Kind);
}
[Fact]
public void BuildMap_TrimsSkipsBlankRowsAndKeepsComparer()
{
var map = PostSettingsLayout.BuildMap(
new[]
{
Row(" Mild Steel ", " MSN "),
Row("", ""),
Row("Stainless", "SS"),
},
StringComparer.OrdinalIgnoreCase
);
Assert.Equal(2, map.Count);
Assert.Equal("MSN", map["mild steel"]);
Assert.Equal("SS", map["Stainless"]);
}
[Fact]
public void BuildMap_RejectsDuplicateAndMissingNames()
{
var duplicate = Assert.Throws<FormatException>(() =>
PostSettingsLayout.BuildMap(
new[] { Row("Mild Steel", "MSN"), Row("mild steel", "X") },
StringComparer.OrdinalIgnoreCase
)
);
Assert.Contains("Row 2", duplicate.Message);
var missing = Assert.Throws<FormatException>(() =>
PostSettingsLayout.BuildMap(new[] { Row(" ", "SS") }, StringComparer.Ordinal)
);
Assert.Contains("Row 1", missing.Message);
}
private static KeyValuePair<string, string> Row(string key, string value) => new(key, value);
private sealed class PlainConfig
{
[DisplayName("Name")]
public string Name { get; set; } = "";
}
private sealed class NestedConfig
{
[PostSetting("Main")]
public string Name { get; set; } = "";
public PlainConfig Child { get; set; } = new();
}
private enum Mode
{
A,
B,
}
private sealed class PartlyMarkedConfig
{
public Mode Extra { get; set; }
[PostSetting("Main")]
public string Name { get; set; } = "";
}
}