초기 커밋: SheetMe 서식생성기 (P0~P5 완료 상태)
레거시 서식생성기(VB.NET WinForms) 대체용 C#/.NET 10 WPF 디자이너. 기준선: 실DB 활성 디자인 1,271건 왕복 의미론 diff 0 / 예외 0, 단위 테스트 49/49. 이 커밋에 함께 포함된 자격증명 분리: - appsettings.json 을 __HOST__/__PASSWORD__ 플레이스홀더로 전환 - 실접속 정보는 appsettings.Development.json 으로 분리(.gitignore 제외, csproj Debug 조건부 복사라 Release 산출물에 실리지 않음) - ConfigLoader 를 환경변수 > Development > appsettings 순 레이어링으로 변경, 미치환 플레이스홀더는 '미설정'으로 간주 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,114 @@
|
||||
using SheetMe.Core.Serialization;
|
||||
|
||||
namespace SheetMe.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// FarPoint Spread XML 파서 테스트.
|
||||
/// 합성 샘플 기반(테스트 DB 의 실물 2건은 셀 없는 기본 골격) — 셀 있는 실서식 확보 시 재검증.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public sealed class FarPointSpreadParserTests
|
||||
{
|
||||
private const string SyntheticXml = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Spread class="M.EMR.UserControl.Spread" assembly="M.EMR.UserControl">
|
||||
<Presentation>
|
||||
<Sheets>
|
||||
<Sheet index="0">
|
||||
<AxisModels>
|
||||
<Row class="FarPoint.Win.Spread.Model.DefaultSheetAxisModel" defaultSize="24" orientation="Vertical" count="500">
|
||||
<Items>
|
||||
<Item index="0"><Size>40</Size></Item>
|
||||
<Item index="2"><Size>60</Size></Item>
|
||||
</Items>
|
||||
</Row>
|
||||
<Column class="FarPoint.Win.Spread.Model.DefaultSheetAxisModel" orientation="Horizontal" count="500">
|
||||
<Items>
|
||||
<Item index="-1"><Size>80</Size></Item>
|
||||
<Item index="1"><Size>120</Size></Item>
|
||||
</Items>
|
||||
</Column>
|
||||
</AxisModels>
|
||||
<SpanModel class="FarPoint.Win.Spread.Model.DefaultSheetSpanModel">
|
||||
<CellRange Row="0" Column="0" RowCount="1" ColumnCount="2" />
|
||||
</SpanModel>
|
||||
</Sheet>
|
||||
</Sheets>
|
||||
</Presentation>
|
||||
<Data>
|
||||
<Sheets>
|
||||
<Sheet index="0">
|
||||
<DataArea class="FarPoint.Win.Spread.Model.DefaultSheetDataModel" rows="500" columns="500">
|
||||
<Cell Row="0" Column="0"><Data type="System.String">평가 항목</Data></Cell>
|
||||
<Cell Row="1" Column="0"><Data type="System.String">보행</Data></Cell>
|
||||
<Cell Row="1" Column="1"><Data type="System.String">3점</Data></Cell>
|
||||
</DataArea>
|
||||
</Sheet>
|
||||
</Sheets>
|
||||
</Data>
|
||||
</Spread>
|
||||
""";
|
||||
|
||||
/// <summary>합성 샘플 — 축 크기/기본값/스팬/셀 추출</summary>
|
||||
[TestMethod]
|
||||
public void Parse_Synthetic_ExtractsAll()
|
||||
{
|
||||
var grid = FarPointSpreadParser.Parse(SyntheticXml);
|
||||
|
||||
Assert.IsNotNull(grid);
|
||||
Assert.AreEqual(24, grid!.DefaultRowHeight, "defaultSize 어트리뷰트");
|
||||
Assert.AreEqual(80, grid.DefaultColWidth, "index=-1 Item 기본값");
|
||||
Assert.AreEqual(40, grid.RowHeightOf(0));
|
||||
Assert.AreEqual(24, grid.RowHeightOf(1), "미지정 행은 기본값");
|
||||
Assert.AreEqual(60, grid.RowHeightOf(2));
|
||||
Assert.AreEqual(120, grid.ColWidthOf(1));
|
||||
Assert.AreEqual(80, grid.ColWidthOf(0));
|
||||
|
||||
Assert.AreEqual(1, grid.Spans.Count);
|
||||
Assert.AreEqual(2, grid.Spans[0].ColSpan);
|
||||
|
||||
Assert.AreEqual(3, grid.Cells.Count);
|
||||
Assert.AreEqual("평가 항목", grid.Cells[0].Text);
|
||||
Assert.AreEqual(1, grid.Cells[2].Row);
|
||||
Assert.AreEqual(1, grid.Cells[2].Col);
|
||||
}
|
||||
|
||||
/// <summary>실물 기본 골격(S999) — 예외 없이 파싱, 기본 크기 유지</summary>
|
||||
[TestMethod]
|
||||
public void Parse_EmptySkeleton_Succeeds()
|
||||
{
|
||||
const string skeleton = """
|
||||
<Spread class="M.EMR.UserControl.Spread">
|
||||
<Presentation>
|
||||
<Sheets><Sheet index="0">
|
||||
<AxisModels>
|
||||
<Row class="FarPoint.Win.Spread.Model.DefaultSheetAxisModel" orientation="Vertical" count="500">
|
||||
<Items><Item index="-1" /></Items>
|
||||
</Row>
|
||||
<Column class="FarPoint.Win.Spread.Model.DefaultSheetAxisModel" orientation="Horizontal" count="500">
|
||||
<Items><Item index="-1" /></Items>
|
||||
</Column>
|
||||
</AxisModels>
|
||||
</Sheet></Sheets>
|
||||
</Presentation>
|
||||
<Data><Sheets><Sheet index="0">
|
||||
<DataArea class="FarPoint.Win.Spread.Model.DefaultSheetDataModel" rows="500" columns="500" />
|
||||
</Sheet></Sheets></Data>
|
||||
</Spread>
|
||||
""";
|
||||
|
||||
var grid = FarPointSpreadParser.Parse(skeleton);
|
||||
|
||||
Assert.IsNotNull(grid);
|
||||
Assert.AreEqual(20, grid!.DefaultRowHeight);
|
||||
Assert.AreEqual(62, grid.DefaultColWidth);
|
||||
Assert.AreEqual(0, grid.Cells.Count);
|
||||
}
|
||||
|
||||
/// <summary>손상 입력 — null 반환(자리표시 렌더 유지)</summary>
|
||||
[TestMethod]
|
||||
public void Parse_Invalid_ReturnsNull()
|
||||
{
|
||||
Assert.IsNull(FarPointSpreadParser.Parse("not xml at all <"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using SheetMe.Core.Serialization;
|
||||
|
||||
namespace SheetMe.Core.Tests;
|
||||
|
||||
/// <summary>JSON 부 포맷 테스트 — 레거시 XML 과의 상호 변환 무손실성.</summary>
|
||||
[TestClass]
|
||||
public sealed class FormJsonTests
|
||||
{
|
||||
private static string SamplePath
|
||||
=> Path.Combine(AppContext.BaseDirectory, "Assets", "sheetdesign_sample.xml");
|
||||
|
||||
/// <summary>XML → 모델 → JSON → 모델 → XML 전체 왕복 — 의미론 diff 0</summary>
|
||||
[TestMethod]
|
||||
public void FullRoundTrip_XmlToJsonToXml_NoSemanticDiff()
|
||||
{
|
||||
var original = File.ReadAllText(SamplePath);
|
||||
var xmlSerializer = new LegacyXmlSerializer();
|
||||
var jsonSerializer = new FormJsonSerializer();
|
||||
|
||||
var document = xmlSerializer.Read(original);
|
||||
var json = jsonSerializer.Write(document);
|
||||
var restored = jsonSerializer.Read(json);
|
||||
var rewrittenXml = xmlSerializer.Write(restored);
|
||||
|
||||
var diffs = XmlSemanticDiff.Compare(original, rewrittenXml);
|
||||
Assert.AreEqual(0, diffs.Count,
|
||||
"XML→JSON→XML 왕복 의미론 diff 발생:\n" + string.Join("\n", diffs));
|
||||
}
|
||||
|
||||
/// <summary>JSON 이중 직렬화 안정성 — 두 번째 출력이 첫 번째와 동일</summary>
|
||||
[TestMethod]
|
||||
public void JsonRoundTrip_IsStable()
|
||||
{
|
||||
var xmlSerializer = new LegacyXmlSerializer();
|
||||
var jsonSerializer = new FormJsonSerializer();
|
||||
|
||||
var document = xmlSerializer.Read(File.ReadAllText(SamplePath));
|
||||
var first = jsonSerializer.Write(document);
|
||||
var second = jsonSerializer.Write(jsonSerializer.Read(first));
|
||||
|
||||
Assert.AreEqual(first, second, "JSON 왕복이 안정적이지 않습니다.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
global using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@@ -0,0 +1,86 @@
|
||||
using SheetMe.Core.Serialization;
|
||||
|
||||
namespace SheetMe.Core.Tests;
|
||||
|
||||
/// <summary>레거시 invariant 문자열 포맷 도우미 테스트 — 실샘플 원문 형식과의 일치 검증.</summary>
|
||||
[TestClass]
|
||||
public sealed class LegacyFormatTests
|
||||
{
|
||||
/// <summary>Point/Size 파싱·생성 — "336, 1064" 왕복</summary>
|
||||
[TestMethod]
|
||||
[DataRow("336, 1064", 336, 1064)]
|
||||
[DataRow("0, 22", 0, 22)]
|
||||
[DataRow("-5, 10", -5, 10)]
|
||||
public void Point_RoundTrip(string text, int x, int y)
|
||||
{
|
||||
var (px, py) = LegacyFormat.ParsePoint(text);
|
||||
Assert.AreEqual(x, px);
|
||||
Assert.AreEqual(y, py);
|
||||
Assert.AreEqual(text.Replace(" ", "").Insert(text.Replace(" ", "").IndexOf(',') + 1, " "),
|
||||
LegacyFormat.FormatPair(px, py));
|
||||
}
|
||||
|
||||
/// <summary>폰트 파싱 — 실샘플 원문 3형태</summary>
|
||||
[TestMethod]
|
||||
public void Font_Parse_SampleForms()
|
||||
{
|
||||
var font1 = LegacyFormat.ParseFont("굴림, 11.25pt, style=Bold");
|
||||
Assert.AreEqual("굴림", font1.Family);
|
||||
Assert.AreEqual(11.25, font1.SizePt);
|
||||
Assert.IsTrue(font1.Bold);
|
||||
Assert.IsFalse(font1.Italic);
|
||||
|
||||
var font2 = LegacyFormat.ParseFont("굴림, 11.25pt");
|
||||
Assert.IsFalse(font2.Bold);
|
||||
|
||||
var font3 = LegacyFormat.ParseFont("돋움, 26.25pt, style=Bold, Underline");
|
||||
Assert.AreEqual("돋움", font3.Family);
|
||||
Assert.IsTrue(font3.Bold);
|
||||
Assert.IsTrue(font3.Underline);
|
||||
}
|
||||
|
||||
/// <summary>폰트 생성 — 레거시 FontConverter invariant 형식과 동일</summary>
|
||||
[TestMethod]
|
||||
public void Font_Format_MatchesLegacy()
|
||||
{
|
||||
Assert.AreEqual("굴림, 11.25pt, style=Bold",
|
||||
LegacyFormat.FormatFont(new LegacyFont { Family = "굴림", SizePt = 11.25, Bold = true }));
|
||||
Assert.AreEqual("굴림, 9pt",
|
||||
LegacyFormat.FormatFont(new LegacyFont { Family = "굴림", SizePt = 9 }));
|
||||
Assert.AreEqual("돋움, 26.25pt, style=Bold, Underline",
|
||||
LegacyFormat.FormatFont(new LegacyFont { Family = "돋움", SizePt = 26.25, Bold = true, Underline = true }));
|
||||
}
|
||||
|
||||
/// <summary>폰트 왕복 — 파싱→생성 원문 유지</summary>
|
||||
[TestMethod]
|
||||
[DataRow("굴림, 11.25pt, style=Bold")]
|
||||
[DataRow("굴림, 11.25pt")]
|
||||
[DataRow("돋움, 26.25pt, style=Bold")]
|
||||
public void Font_RoundTrip(string original)
|
||||
{
|
||||
Assert.AreEqual(original, LegacyFormat.FormatFont(LegacyFormat.ParseFont(original)));
|
||||
}
|
||||
|
||||
/// <summary>색 파싱 — RGB 성분/명명색/시스템색</summary>
|
||||
[TestMethod]
|
||||
public void Color_Parse_SampleForms()
|
||||
{
|
||||
Assert.AreEqual(((byte)255, (byte)224, (byte)224, (byte)224), LegacyFormat.ParseColor("224, 224, 224"));
|
||||
Assert.AreEqual(((byte)255, (byte)255, (byte)255, (byte)255), LegacyFormat.ParseColor("White"));
|
||||
|
||||
var transparent = LegacyFormat.ParseColor("Transparent");
|
||||
Assert.AreEqual(0, transparent.A);
|
||||
|
||||
// 시스템색 — 성분값은 OS 테마에 따라 다르므로 해석 성공 여부만 확인
|
||||
var system = LegacyFormat.ParseColor("ControlDarkDark");
|
||||
Assert.IsTrue(system.A > 0, "시스템색 해석 실패");
|
||||
}
|
||||
|
||||
/// <summary>색 생성 — "R, G, B" invariant 형식</summary>
|
||||
[TestMethod]
|
||||
public void Color_Format_MatchesLegacy()
|
||||
{
|
||||
Assert.AreEqual("224, 224, 224", LegacyFormat.FormatColor(255, 224, 224, 224));
|
||||
Assert.AreEqual("128, 255, 0, 0", LegacyFormat.FormatColor(128, 255, 0, 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using SheetMe.Core.Catalog;
|
||||
using SheetMe.Core.Serialization;
|
||||
|
||||
namespace SheetMe.Core.Tests;
|
||||
|
||||
/// <summary>레거시 타입 매핑 카탈로그 테스트.</summary>
|
||||
[TestClass]
|
||||
public sealed class LegacyTypeCatalogTests
|
||||
{
|
||||
/// <summary>레거시 클래스 → 중립 타입 매핑 (변형 포함)</summary>
|
||||
[TestMethod]
|
||||
[DataRow("M.EMR.UserControl.TextBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "TextBox")]
|
||||
[DataRow("M.EMR.UserControl.MaskedTextBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "MaskedTextBox")]
|
||||
[DataRow("M.EMR.UserControl.MFormatLabel, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Label")]
|
||||
[DataRow("M.EMR.UserControl.MSequence, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Label")]
|
||||
[DataRow("M.EMR.UserControl.CheckLabel, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "CheckBox")]
|
||||
[DataRow("M.EMR.UserControl.MCheckedListBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "CheckList")]
|
||||
[DataRow("M.EMR.UserControl.Panel2, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Panel")]
|
||||
[DataRow("M.EMR.UserControl.MExpandablePanel, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Panel")]
|
||||
[DataRow("M.EMR.UserControl.MLine, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Line")]
|
||||
[DataRow("M.EMR.UserControl.MPictureBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "PictureBox")]
|
||||
[DataRow("M.EMR.UserControl.MCalcBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "CalcBox")]
|
||||
public void Resolve_MapsLegacyTypes(string aqn, string expected)
|
||||
{
|
||||
Assert.AreEqual(expected, LegacyTypeCatalog.Resolve(aqn));
|
||||
}
|
||||
|
||||
/// <summary>미지원 타입 → Placeholder</summary>
|
||||
[TestMethod]
|
||||
[DataRow("M.EMR.UserControl.MSign, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
|
||||
[DataRow("M.EMR.UserControl.MFile, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
|
||||
[DataRow("Some.Unknown.CustomControl, Some.Assembly")]
|
||||
public void Resolve_UnknownTypes_ReturnPlaceholder(string aqn)
|
||||
{
|
||||
Assert.AreEqual(LegacyTypeCatalog.PlaceholderType, LegacyTypeCatalog.Resolve(aqn));
|
||||
}
|
||||
|
||||
/// <summary>네이티브 지원 승격 타입 — MButton/MDataTable/Spread(렌더 전용)</summary>
|
||||
[TestMethod]
|
||||
[DataRow("M.EMR.UserControl.MButton, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Button")]
|
||||
[DataRow("M.EMR.UserControl.MDataTable, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "DataTable")]
|
||||
[DataRow("M.EMR.UserControl.Spread, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Spread")]
|
||||
public void Resolve_NativeUpgradedTypes(string aqn, string expected)
|
||||
{
|
||||
Assert.AreEqual(expected, LegacyTypeCatalog.Resolve(aqn));
|
||||
}
|
||||
|
||||
/// <summary>팔레트 14종 전부 신규 생성 AQN 보유 + 왕복 매핑 일관성</summary>
|
||||
[TestMethod]
|
||||
public void AqnOf_AllPaletteTypes_RoundTripConsistent()
|
||||
{
|
||||
foreach (var descriptor in ControlRegistry.All)
|
||||
{
|
||||
var aqn = LegacyTypeCatalog.AqnOf(descriptor.Type);
|
||||
Assert.IsNotNull(aqn, $"{descriptor.Type} 의 AQN 이 없습니다.");
|
||||
Assert.AreEqual(descriptor.Type, LegacyTypeCatalog.Resolve(aqn!),
|
||||
$"{descriptor.Type} 의 AQN({aqn})이 자기 자신으로 해석되지 않습니다.");
|
||||
StringAssert.Contains(aqn!, "M.EMR.UserControl", $"{descriptor.Type} AQN 어셈블리 누락");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>MDesignerHost 판정</summary>
|
||||
[TestMethod]
|
||||
public void IsHost_DetectsDesignerHost()
|
||||
{
|
||||
Assert.IsTrue(LegacyTypeCatalog.IsHost(LegacyTypeCatalog.HostAqn));
|
||||
Assert.IsFalse(LegacyTypeCatalog.IsHost("M.EMR.UserControl.TextBox, M.EMR.UserControl"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using SheetMe.Core.Models;
|
||||
using SheetMe.Core.Serialization;
|
||||
|
||||
namespace SheetMe.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 레거시 XML 왕복 무결성 테스트 — P0 게이트.
|
||||
/// 실서식 샘플(sheetdesign_sample.xml)을 Read→Write 했을 때 의미론 diff 가 0 이어야 한다.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public sealed class LegacyXmlRoundTripTests
|
||||
{
|
||||
private static string SamplePath
|
||||
=> Path.Combine(AppContext.BaseDirectory, "Assets", "sheetdesign_sample.xml");
|
||||
|
||||
/// <summary>실서식 샘플 왕복 — 의미론 diff 0</summary>
|
||||
[TestMethod]
|
||||
public void RoundTrip_RealSample_NoSemanticDiff()
|
||||
{
|
||||
var original = File.ReadAllText(SamplePath);
|
||||
var serializer = new LegacyXmlSerializer();
|
||||
|
||||
var document = serializer.Read(original);
|
||||
var rewritten = serializer.Write(document);
|
||||
|
||||
var diffs = XmlSemanticDiff.Compare(original, rewritten);
|
||||
Assert.AreEqual(0, diffs.Count,
|
||||
"왕복 의미론 diff 발생:\n" + string.Join("\n", diffs));
|
||||
}
|
||||
|
||||
/// <summary>실서식 샘플 이중 왕복 — Write 출력이 안정(고정점)이어야 한다</summary>
|
||||
[TestMethod]
|
||||
public void RoundTrip_RealSample_SecondPassIsStable()
|
||||
{
|
||||
var original = File.ReadAllText(SamplePath);
|
||||
var serializer = new LegacyXmlSerializer();
|
||||
|
||||
var first = serializer.Write(serializer.Read(original));
|
||||
var second = serializer.Write(serializer.Read(first));
|
||||
|
||||
Assert.AreEqual(first, second, "이중 왕복 결과가 문자열 수준에서 안정적이지 않습니다.");
|
||||
}
|
||||
|
||||
/// <summary>실서식 샘플 구조 확인 — 페이지/컨트롤 수·타입 매핑</summary>
|
||||
[TestMethod]
|
||||
public void Read_RealSample_ParsesStructure()
|
||||
{
|
||||
var serializer = new LegacyXmlSerializer();
|
||||
var document = serializer.Read(File.ReadAllText(SamplePath));
|
||||
|
||||
Assert.AreEqual(1, document.Pages.Count, "샘플은 단일 페이지여야 합니다.");
|
||||
var page = document.Pages[0];
|
||||
Assert.AreEqual("MDesignerHost1", page.Root.Id);
|
||||
Assert.AreEqual(823, page.Width, "루트 Size 리프트(823×1250) 실패");
|
||||
Assert.AreEqual(1250, page.Height);
|
||||
|
||||
// 샘플 실측: MLine 34, TextBox 33, Label 27, RadioButton 15, Panel 6, MSequence 1, MPictureBox 1
|
||||
var allControls = Flatten(page.Controls).ToList();
|
||||
Assert.AreEqual(117, allControls.Count, "전체 컨트롤 수(루트 제외) 불일치");
|
||||
Assert.AreEqual(34, allControls.Count(c => c.Type == "Line"));
|
||||
Assert.AreEqual(33, allControls.Count(c => c.Type == "TextBox"));
|
||||
Assert.AreEqual(28, allControls.Count(c => c.Type == "Label"), "Label 27 + MSequence 1 = 28");
|
||||
Assert.AreEqual(15, allControls.Count(c => c.Type == "RadioButton"));
|
||||
Assert.AreEqual(6, allControls.Count(c => c.Type == "Panel"));
|
||||
Assert.AreEqual(1, allControls.Count(c => c.Type == "PictureBox"));
|
||||
Assert.AreEqual(0, allControls.Count(c => c.Type == LegacyTypeCatalog.PlaceholderType),
|
||||
"샘플에는 미지원 타입이 없어야 합니다.");
|
||||
}
|
||||
|
||||
/// <summary>다중 페이지 병합 규약 — Sheet 루트 1개에 MDesignerHost Object N개</summary>
|
||||
[TestMethod]
|
||||
public void RoundTrip_MultiPage_PreservesPages()
|
||||
{
|
||||
const string xml =
|
||||
"<Sheet name=\"SheetDesignInfo\">" +
|
||||
"<Object type=\"M.EMR.UserControl.MDesignerHost, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"MDesignerHost1\" children=\"Controls\" DisplaySequence=\"0\">" +
|
||||
"<Property name=\"Size\">720, 856</Property>" +
|
||||
"</Object>" +
|
||||
"<Object type=\"M.EMR.UserControl.MDesignerHost, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"MDesignerHost1\" children=\"Controls\" DisplaySequence=\"0\">" +
|
||||
"<Property name=\"Size\">720, 856</Property>" +
|
||||
"</Object>" +
|
||||
"</Sheet>";
|
||||
|
||||
var serializer = new LegacyXmlSerializer();
|
||||
var document = serializer.Read(xml);
|
||||
|
||||
Assert.AreEqual(2, document.Pages.Count);
|
||||
|
||||
var rewritten = serializer.Write(document);
|
||||
var diffs = XmlSemanticDiff.Compare(xml, rewritten);
|
||||
Assert.AreEqual(0, diffs.Count, string.Join("\n", diffs));
|
||||
}
|
||||
|
||||
/// <summary>신규 컨트롤 추가 후 쓰기 — Location/Size/AQN 이 레거시 형식으로 생성</summary>
|
||||
[TestMethod]
|
||||
public void Write_NewControl_EmitsLegacyFormat()
|
||||
{
|
||||
var serializer = new LegacyXmlSerializer();
|
||||
var document = new FormDocument();
|
||||
var page = LegacyXmlSerializer.CreateEmptyPage(1);
|
||||
document.Pages.Add(page);
|
||||
|
||||
var textBox = new ControlElement
|
||||
{
|
||||
Type = "TextBox",
|
||||
Id = "TextBox1",
|
||||
Bounds = new LayoutRect { X = 120, Y = 88, W = 200, H = 24 },
|
||||
};
|
||||
textBox.Props.SetText("Text", "환자명");
|
||||
page.Controls.Add(textBox);
|
||||
|
||||
var xml = serializer.Write(document);
|
||||
|
||||
StringAssert.Contains(xml, "M.EMR.UserControl.TextBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
|
||||
StringAssert.Contains(xml, "<Property name=\"Location\">120, 88</Property>");
|
||||
StringAssert.Contains(xml, "<Property name=\"Size\">200, 24</Property>");
|
||||
StringAssert.Contains(xml, "<Property name=\"Text\">환자명</Property>");
|
||||
|
||||
// 재읽기 시 동일 구조
|
||||
var reread = serializer.Read(xml);
|
||||
Assert.AreEqual(1, reread.Pages[0].Controls.Count);
|
||||
Assert.AreEqual("TextBox", reread.Pages[0].Controls[0].Type);
|
||||
Assert.AreEqual(120, reread.Pages[0].Controls[0].Bounds.X);
|
||||
}
|
||||
|
||||
/// <summary>컨테이너 중첩 시 LocationOnBase 재계산 — 절대좌표(부모 누적) 규약</summary>
|
||||
[TestMethod]
|
||||
public void Write_NestedControl_RecomputesLocationOnBase()
|
||||
{
|
||||
const string xml =
|
||||
"<Sheet name=\"SheetDesignInfo\">" +
|
||||
"<Object type=\"M.EMR.UserControl.MDesignerHost, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"MDesignerHost1\" children=\"Controls\" DisplaySequence=\"0\">" +
|
||||
"<Object type=\"M.EMR.UserControl.Panel, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"Panel1\" children=\"Controls\" DisplaySequence=\"0\">" +
|
||||
"<Object type=\"M.EMR.UserControl.TextBox, M.EMR.UserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" name=\"TextBox1\" children=\"Controls\" DisplaySequence=\"0\">" +
|
||||
"<Property name=\"LocationOnBase\">0, 0</Property>" +
|
||||
"<Property name=\"Location\">10, 20</Property>" +
|
||||
"<Property name=\"Size\">100, 22</Property>" +
|
||||
"</Object>" +
|
||||
"<Property name=\"Location\">50, 60</Property>" +
|
||||
"<Property name=\"Size\">200, 100</Property>" +
|
||||
"</Object>" +
|
||||
"<Property name=\"Size\">720, 856</Property>" +
|
||||
"</Object>" +
|
||||
"</Sheet>";
|
||||
|
||||
var serializer = new LegacyXmlSerializer();
|
||||
var document = serializer.Read(xml);
|
||||
var rewritten = serializer.Write(document);
|
||||
|
||||
// Panel(50,60) 안의 TextBox(10,20) → LocationOnBase = 60, 80 (레거시 writer 재계산 규약)
|
||||
StringAssert.Contains(rewritten, "<Property name=\"LocationOnBase\">60, 80</Property>");
|
||||
}
|
||||
|
||||
private static IEnumerable<ControlElement> Flatten(IEnumerable<ControlElement> controls)
|
||||
{
|
||||
foreach (var control in controls)
|
||||
{
|
||||
yield return control;
|
||||
foreach (var child in Flatten(control.Children))
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using SheetMe.Data.Stores;
|
||||
|
||||
namespace SheetMe.Core.Tests;
|
||||
|
||||
/// <summary>E_SctMst 트리 순회(레거시 bzSaveSheetDesignNControlInfo 이식) 테스트.</summary>
|
||||
[TestClass]
|
||||
public sealed class SctMstXmlWalkerTests
|
||||
{
|
||||
private static string SamplePath
|
||||
=> Path.Combine(AppContext.BaseDirectory, "Assets", "sheetdesign_sample.xml");
|
||||
|
||||
/// <summary>알파벳 시퀀스 — 레거시 GetNextAlphabetSequence 와 동일</summary>
|
||||
[TestMethod]
|
||||
[DataRow("A", "B")]
|
||||
[DataRow("Y", "Z")]
|
||||
[DataRow("Z", "AA")]
|
||||
[DataRow("AA", "AB")]
|
||||
[DataRow("AZ", "BA")]
|
||||
[DataRow("ZZ", "AAA")]
|
||||
[DataRow("AAZ", "ABA")]
|
||||
public void NextAlphabetSequence_MatchesLegacy(string current, string expected)
|
||||
{
|
||||
Assert.AreEqual(expected, SctMstXmlWalker.NextAlphabetSequence(current));
|
||||
}
|
||||
|
||||
/// <summary>실샘플 순회 — 전체 Object 수(118)와 루트/자식 규약</summary>
|
||||
[TestMethod]
|
||||
public void Walk_RealSample_ProducesLegacyRows()
|
||||
{
|
||||
var rows = SctMstXmlWalker.Walk(File.ReadAllText(SamplePath));
|
||||
|
||||
Assert.AreEqual(118, rows.Count, "루트(MDesignerHost) 포함 118행이어야 합니다.");
|
||||
|
||||
var root = rows[0];
|
||||
Assert.AreEqual("MDesignerHost1", root.ObjName);
|
||||
Assert.AreEqual("A".PadRight(15), root.ObjId);
|
||||
Assert.AreEqual("M.EMR.UserControl.MDesignerHost", root.ObjType);
|
||||
Assert.AreEqual(string.Empty.PadRight(15), root.ParentId);
|
||||
|
||||
// 첫 자식: ObjId = AAA...? 루트 A 의 첫 자식 = A + AA = "AAA"? 아님 — 부모ID+AA = "AAA" 가 아니라 "A"+"AA"="AAA"
|
||||
var firstChild = rows[1];
|
||||
Assert.AreEqual("AAA".PadRight(15), firstChild.ObjId, "루트 A 의 첫 자식은 A+AA=AAA");
|
||||
Assert.AreEqual("A".PadRight(15), firstChild.ParentId);
|
||||
|
||||
// 모든 행의 ObjId 는 15자 패딩
|
||||
Assert.IsTrue(rows.All(r => r.ObjId.Length == 15));
|
||||
// ObjType 은 AQN 이 아니라 풀 클래스명(콤마 없음)
|
||||
Assert.IsTrue(rows.All(r => !r.ObjType.Contains(',')));
|
||||
}
|
||||
|
||||
/// <summary>Text Property 가 SctObjTxt 로 추출되는지</summary>
|
||||
[TestMethod]
|
||||
public void Walk_ExtractsText()
|
||||
{
|
||||
const string xml =
|
||||
"<Sheet name=\"SheetDesignInfo\">" +
|
||||
"<Object type=\"M.EMR.UserControl.MDesignerHost, M.EMR.UserControl\" name=\"MDesignerHost1\" children=\"Controls\" DisplaySequence=\"0\">" +
|
||||
"<Object type=\"M.EMR.UserControl.Label, M.EMR.UserControl\" name=\"lbl1\" children=\"Controls\" DisplaySequence=\"3\">" +
|
||||
"<Property name=\"Text\">제목입니다</Property>" +
|
||||
"</Object>" +
|
||||
"</Object>" +
|
||||
"</Sheet>";
|
||||
|
||||
var rows = SctMstXmlWalker.Walk(xml);
|
||||
|
||||
Assert.AreEqual(2, rows.Count);
|
||||
Assert.AreEqual("제목입니다", rows[1].ObjText);
|
||||
Assert.AreEqual("3", rows[1].ObjSeq);
|
||||
Assert.AreEqual("M.EMR.UserControl.Label", rows[1].ObjType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>SheetMe.Core.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\SheetMe.Core\SheetMe.Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\SheetMe.Data\SheetMe.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.7.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.7.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Assets\**\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user