초기 커밋: 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:
@@ -0,0 +1,408 @@
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using SheetMe.Core.Serialization;
|
||||
using SheetMe.Designer.DataBusiness;
|
||||
using SheetMe.Designer.Services;
|
||||
using SheetMe.Designer.ViewModels;
|
||||
using SheetMe.Designer.ViewModels.Controls;
|
||||
|
||||
namespace SheetMe.Designer.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// 편집 코어 자동 검증(--edit-smoke) — 실제 마우스 없이 InteractionController 에 포인터 시퀀스를 주입해
|
||||
/// 선택/이동/리사이즈/마퀴/복사/붙여넣기/삭제/Undo/저장왕복을 검사한다.
|
||||
/// </summary>
|
||||
public static class EditSmoke
|
||||
{
|
||||
#region Methods
|
||||
/// <summary>스모크 실행 — 결과를 리포트 파일에 기록, 실패 있으면 1 반환</summary>
|
||||
public static int Run(string reportPath)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var failed = 0;
|
||||
|
||||
void Check(string name, bool condition, string? detail = null)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
failed++;
|
||||
}
|
||||
lines.Add($"{(condition ? "PASS" : "FAIL")} {name}{(condition || detail is null ? "" : " — " + detail)}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var business = new FormDesignDataBusiness();
|
||||
var serializer = new LegacyXmlSerializer();
|
||||
var designer = new DesignerViewModel(business.CreateNew());
|
||||
var none = new PointerContext(false, false, false, 1);
|
||||
var alt = new PointerContext(false, false, true, 1);
|
||||
var ctrl = new PointerContext(true, false, false, 1);
|
||||
|
||||
ControlViewModel Find(string type)
|
||||
=> designer.Pages[0].Controls.First(c => c.Type == type);
|
||||
|
||||
// 1) 팔레트 배치
|
||||
designer.AddControlAt("TextBox", new Point(100, 100));
|
||||
designer.AddControlAt("Label", new Point(400, 60));
|
||||
designer.AddControlAt("CheckBox", new Point(200, 260));
|
||||
Check("배치: VM/모델 3개", designer.Pages[0].Controls.Count == 3
|
||||
&& designer.Document.Pages[0].Controls.Count == 3);
|
||||
Check("배치: 신규가 선택됨", designer.Selection.Items.Count == 1);
|
||||
|
||||
// 2) 클릭 선택 — 컨트롤 중심(핸들 반경 밖)
|
||||
var textBox = Find("TextBox");
|
||||
var clickPoint = new Point(textBox.X + textBox.Width / 2, textBox.Y + textBox.Height / 2);
|
||||
designer.Interaction.PointerDown(clickPoint, none);
|
||||
designer.Interaction.PointerUp(clickPoint, none);
|
||||
Check("클릭 선택: Primary=TextBox", designer.Selection.Primary == textBox);
|
||||
|
||||
// 3) 드래그 이동(Alt=자유 이동 — 결정적 검증)
|
||||
var origX = textBox.X;
|
||||
var origY = textBox.Y;
|
||||
designer.Interaction.PointerDown(clickPoint, none);
|
||||
designer.Interaction.PointerMove(new Point(clickPoint.X + 33, clickPoint.Y + 47), alt);
|
||||
designer.Interaction.PointerUp(new Point(clickPoint.X + 33, clickPoint.Y + 47), alt);
|
||||
Check("드래그 이동 +33,+47", textBox.X == origX + 33 && textBox.Y == origY + 47,
|
||||
$"실제 ({textBox.X},{textBox.Y}) 기대 ({origX + 33},{origY + 47})");
|
||||
|
||||
// 4) Undo → 위치 원복(VM 재생성되므로 재조회)
|
||||
designer.Undo.Undo();
|
||||
var textBoxAfterUndo = Find("TextBox");
|
||||
Check("Undo: 위치 원복", textBoxAfterUndo.X == origX && textBoxAfterUndo.Y == origY,
|
||||
$"실제 ({textBoxAfterUndo.X},{textBoxAfterUndo.Y})");
|
||||
|
||||
// 5) Redo → 이동 재적용
|
||||
designer.Undo.Redo();
|
||||
var textBoxAfterRedo = Find("TextBox");
|
||||
Check("Redo: 이동 재적용", textBoxAfterRedo.X == origX + 33 && textBoxAfterRedo.Y == origY + 47);
|
||||
|
||||
// 6) 리사이즈 — SE 핸들(4번) 드래그
|
||||
var target = Find("TextBox");
|
||||
designer.Selection.SetSingle(target);
|
||||
var bbox = designer.SelectionWorldBounds()!.Value;
|
||||
var seHandle = new Point(bbox.Right, bbox.Bottom);
|
||||
var origW = target.Width;
|
||||
var origH = target.Height;
|
||||
designer.Interaction.PointerDown(seHandle, none);
|
||||
designer.Interaction.PointerMove(new Point(seHandle.X + 40, seHandle.Y + 18), alt);
|
||||
designer.Interaction.PointerUp(new Point(seHandle.X + 40, seHandle.Y + 18), alt);
|
||||
Check("리사이즈 +40,+18", target.Width == origW + 40 && target.Height == origH + 18,
|
||||
$"실제 ({target.Width},{target.Height}) 기대 ({origW + 40},{origH + 18})");
|
||||
|
||||
// 7) 마퀴 다중선택 — 전체를 덮는 사각형
|
||||
designer.Selection.Clear();
|
||||
designer.Interaction.PointerDown(new Point(1, 1), none);
|
||||
designer.Interaction.PointerMove(new Point(700, 700), none);
|
||||
designer.Interaction.PointerUp(new Point(700, 700), none);
|
||||
Check("마퀴: 3개 선택", designer.Selection.Items.Count == 3,
|
||||
$"실제 {designer.Selection.Items.Count}");
|
||||
|
||||
// 8) Ctrl+클릭 토글 해제
|
||||
var label = Find("Label");
|
||||
var labelPoint = new Point(label.X + 3, label.Y + 3);
|
||||
designer.Interaction.PointerDown(labelPoint, ctrl);
|
||||
designer.Interaction.PointerUp(labelPoint, ctrl);
|
||||
Check("Ctrl+클릭: 토글 해제 → 2개", designer.Selection.Items.Count == 2);
|
||||
|
||||
// 9) 복사/붙여넣기
|
||||
designer.Selection.SetSingle(Find("CheckBox"));
|
||||
designer.CopySelection();
|
||||
designer.Paste();
|
||||
Check("붙여넣기: 4개 + 새 Id", designer.Pages[0].Controls.Count == 4
|
||||
&& designer.Pages[0].Controls.Count(c => c.Type == "CheckBox") == 2
|
||||
&& designer.Pages[0].Controls.Where(c => c.Type == "CheckBox").Select(c => c.Id).Distinct().Count() == 2);
|
||||
|
||||
// 10) 삭제
|
||||
designer.DeleteSelection();
|
||||
Check("삭제: 3개", designer.Pages[0].Controls.Count == 3);
|
||||
|
||||
// 11) z-order — TextBox 맨 뒤로 (모델 마지막 = VM 첫번째)
|
||||
designer.Selection.SetSingle(Find("TextBox"));
|
||||
designer.ReorderSelection(toFront: false);
|
||||
Check("맨 뒤로: 모델 끝/VM 처음",
|
||||
designer.Document.Pages[0].Controls[^1].Type == "TextBox"
|
||||
&& designer.Pages[0].Controls[0].Type == "TextBox");
|
||||
|
||||
// 12) 넛지
|
||||
var nudgeTarget = Find("Label");
|
||||
designer.Selection.SetSingle(nudgeTarget);
|
||||
var beforeNudgeX = nudgeTarget.X;
|
||||
designer.NudgeSelection(1, 0);
|
||||
designer.NudgeSelection(1, 0);
|
||||
Check("넛지 +2", nudgeTarget.X == beforeNudgeX + 2);
|
||||
|
||||
// 13) 저장 → 재로드 → 재직렬화 동일(왕복 안정)
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), "sheetme_editsmoke.xml");
|
||||
business.SaveXmlFile(designer.Document, tempPath);
|
||||
var reloaded = business.OpenXmlFile(tempPath);
|
||||
var xmlA = serializer.Write(designer.Document);
|
||||
var xmlB = serializer.Write(reloaded);
|
||||
Check("저장/재로드 왕복 동일", xmlA == xmlB);
|
||||
|
||||
// 14) 신규 컨트롤 XML 에 레거시 필수 요소 포함
|
||||
Check("XML: AQN/Location/Name 포함",
|
||||
xmlA.Contains("M.EMR.UserControl.TextBox,") && xmlA.Contains("<Property name=\"Location\">")
|
||||
&& xmlA.Contains("<Property name=\"Name\">"));
|
||||
|
||||
// 15) 인스펙터 — 선택 시 행 구성 + Text 커밋
|
||||
var inspectorTarget = Find("Label");
|
||||
designer.Selection.SetSingle(inspectorTarget);
|
||||
Check("인스펙터: 행 생성", designer.Inspector.Rows.Count > 5,
|
||||
$"행 {designer.Inspector.Rows.Count}");
|
||||
var textRow = designer.Inspector.Rows
|
||||
.OfType<ViewModels.Inspector.MultilineTextRowViewModel>()
|
||||
.FirstOrDefault(r => r.Label == "텍스트");
|
||||
Check("인스펙터: 텍스트 행 존재", textRow is not null);
|
||||
if (textRow is not null)
|
||||
{
|
||||
textRow.ValueText = "수정된 라벨";
|
||||
Check("인스펙터: Text 커밋 → 모델 반영",
|
||||
inspectorTarget.Model.Props.GetText("Text") == "수정된 라벨"
|
||||
&& inspectorTarget.Text == "수정된 라벨");
|
||||
Check("인스펙터: 커밋이 Undo 스텝", designer.Undo.CanUndo);
|
||||
}
|
||||
|
||||
// 16) 인스펙터 — 이름 변경
|
||||
var nameRow = designer.Inspector.Rows
|
||||
.OfType<ViewModels.Inspector.TextRowViewModel>()
|
||||
.FirstOrDefault(r => r.Label == "이름");
|
||||
if (nameRow is not null)
|
||||
{
|
||||
nameRow.ValueText = "lblTitle";
|
||||
Check("인스펙터: 개명 반영", inspectorTarget.Id == "lblTitle");
|
||||
}
|
||||
|
||||
// 17) 페이지 추가/삭제 + 용지 크기
|
||||
designer.AddPage();
|
||||
Check("페이지 추가: 2페이지", designer.Pages.Count == 2
|
||||
&& designer.Document.Pages.Count == 2);
|
||||
designer.SelectedPage = designer.Pages[1];
|
||||
designer.ApplyPaperSize(794, 1123);
|
||||
Check("용지 크기 변경(A4)", designer.Pages[1].WidthDip == 794 && designer.Pages[1].HeightDip == 1123);
|
||||
designer.RemoveSelectedPageForSmoke();
|
||||
Check("페이지 삭제: 1페이지", designer.Pages.Count == 1);
|
||||
|
||||
// 18) 컨테이너 드롭 — Panel 배치 후 내부에 TextBox 드롭
|
||||
designer.AddControlAt("Panel", new Point(400, 400));
|
||||
var panel = designer.Pages[0].Controls.OfType<PanelViewModel>().First();
|
||||
designer.AddControlAt("TextBox", new Point(panel.X + 30, panel.Y + 30));
|
||||
Check("컨테이너 드롭: 자식 추가", panel.Model.Children.Count == 1
|
||||
&& panel.Children.Count == 1
|
||||
&& panel.Model.Children[0].Type == "TextBox");
|
||||
|
||||
// 19) 컨테이너 자식 직접 편집 — 히트테스트/이동/삭제
|
||||
var panelVm = designer.Pages[0].Controls.OfType<PanelViewModel>().First();
|
||||
var child = panelVm.Children[0];
|
||||
var childWorld = designer.WorldBoundsOf(child);
|
||||
var childHit = designer.HitTestControl(new Point(childWorld.X + childWorld.Width / 2, childWorld.Y + childWorld.Height / 2));
|
||||
Check("자식 히트테스트", childHit == child, $"실제 {childHit?.Id}");
|
||||
|
||||
designer.Selection.SetSingle(child);
|
||||
var childOrigX = child.X;
|
||||
var center = new Point(childWorld.X + childWorld.Width / 2, childWorld.Y + childWorld.Height / 2);
|
||||
designer.Interaction.PointerDown(center, none);
|
||||
designer.Interaction.PointerMove(new Point(center.X + 15, center.Y + 10), alt);
|
||||
designer.Interaction.PointerUp(new Point(center.X + 15, center.Y + 10), alt);
|
||||
Check("자식 이동(+15,+10 상대좌표)", child.X == childOrigX + 15,
|
||||
$"실제 X={child.X} 기대 {childOrigX + 15}");
|
||||
|
||||
designer.Selection.SetSingle(child);
|
||||
designer.DeleteSelection();
|
||||
Check("자식 삭제", panelVm.Children.Count == 0 && panelVm.Model.Children.Count == 0);
|
||||
|
||||
// 20) 그룹/해제 — 그룹 단위 클릭 선택
|
||||
designer.Selection.Set(new[] { Find("TextBox"), Find("Label") });
|
||||
designer.GroupSelection();
|
||||
designer.Selection.Clear();
|
||||
var member = Find("TextBox");
|
||||
var memberPoint = new Point(member.X + member.Width / 2, member.Y + member.Height / 2);
|
||||
designer.Interaction.PointerDown(memberPoint, none);
|
||||
designer.Interaction.PointerUp(memberPoint, none);
|
||||
Check("그룹 클릭 → 그룹 전체 선택", designer.Selection.Items.Count == 2,
|
||||
$"실제 {designer.Selection.Items.Count}");
|
||||
designer.UngroupSelection();
|
||||
designer.Selection.Clear();
|
||||
designer.Interaction.PointerDown(memberPoint, none);
|
||||
designer.Interaction.PointerUp(memberPoint, none);
|
||||
Check("그룹 해제 후 단일 선택", designer.Selection.Items.Count == 1);
|
||||
|
||||
// 20-1) 인라인 텍스트 편집 — 더블클릭 요청 이벤트 + 커밋
|
||||
var inlineTarget = Find("Label");
|
||||
ControlViewModel? inlineRequested = null;
|
||||
designer.InlineEditRequested += vm => inlineRequested = vm;
|
||||
var inlineWorld = designer.WorldBoundsOf(inlineTarget);
|
||||
var inlineCenter = new Point(inlineWorld.X + inlineWorld.Width / 2, inlineWorld.Y + inlineWorld.Height / 2);
|
||||
designer.Interaction.PointerDown(inlineCenter, new PointerContext(false, false, false, 2));
|
||||
designer.Interaction.PointerUp(inlineCenter, new PointerContext(false, false, false, 2));
|
||||
Check("더블클릭 → 인라인 편집 요청", inlineRequested == inlineTarget,
|
||||
$"실제 {inlineRequested?.Id ?? "null"}");
|
||||
designer.CommitInlineText(inlineTarget, "인라인 수정됨");
|
||||
Check("인라인 커밋 → 모델/Undo 반영",
|
||||
inlineTarget.Text == "인라인 수정됨"
|
||||
&& inlineTarget.Model.Props.GetText("Text") == "인라인 수정됨"
|
||||
&& designer.Undo.CanUndo);
|
||||
|
||||
// 20-2) 폰트 일괄 변경 — 집계 + 적용
|
||||
var usage = designer.CollectFontUsage();
|
||||
Check("폰트 집계: 조합 존재", usage.Count >= 2, $"실제 {usage.Count}");
|
||||
var applied = designer.ApplyFontBulk(usage, new Core.Serialization.LegacyFont
|
||||
{
|
||||
Family = "돋움",
|
||||
SizePt = 12,
|
||||
Bold = true,
|
||||
});
|
||||
Check("폰트 일괄 적용: 대상 수", applied >= 2, $"실제 {applied}");
|
||||
var fontTarget = Find("Label");
|
||||
Check("폰트 일괄 적용: 모델 반영",
|
||||
fontTarget.Model.Props.GetText("Font") == "돋움, 12pt, style=Bold"
|
||||
&& fontTarget.EffectiveFont.Family == "돋움" && fontTarget.EffectiveFont.Bold);
|
||||
designer.Undo.Undo();
|
||||
Check("폰트 일괄 적용: Undo 1스텝 원복",
|
||||
Find("Label").Model.Props.GetText("Font") != "돋움, 12pt, style=Bold");
|
||||
|
||||
// 20-3) 문서 간 복사/붙여넣기 — A 서식에서 복사 → B 서식에 붙여넣기(클립보드 공유)
|
||||
var designerB = new DesignerViewModel(business.CreateNew());
|
||||
designer.Selection.SetSingle(Find("TextBox"));
|
||||
designer.CopySelection();
|
||||
var beforePasteB = designerB.Pages[0].Controls.Count;
|
||||
designerB.Paste();
|
||||
Check("문서 간 붙여넣기: B 문서에 추가", designerB.Pages[0].Controls.Count == beforePasteB + 1
|
||||
&& designerB.Pages[0].Controls.Any(c => c.Type == "TextBox"),
|
||||
$"실제 {designerB.Pages[0].Controls.Count}");
|
||||
Check("문서 간 붙여넣기: A 문서 불변", designer.Pages[0].Controls.Count(c => c.Type == "TextBox") == 1);
|
||||
|
||||
// 20-4) 배치 명령 — 같은 크기/간격 균등/페이지 가운데/잘라내기
|
||||
designer.AddControlAt("Label", new Point(100, 500));
|
||||
designer.AddControlAt("Label", new Point(300, 520));
|
||||
designer.AddControlAt("Label", new Point(560, 540));
|
||||
var arrangeTargets = designer.Pages[0].Controls.Where(c => c.Y >= 480).ToList();
|
||||
designer.Selection.Set(arrangeTargets, arrangeTargets[0]);
|
||||
|
||||
designer.SizeToControl("Both");
|
||||
Check("같은 크기로: 기준에 통일",
|
||||
arrangeTargets.All(c => c.Width == arrangeTargets[0].Width && c.Height == arrangeTargets[0].Height));
|
||||
|
||||
designer.AdjustSpacing("HorizEqual");
|
||||
var sorted = arrangeTargets.OrderBy(c => c.X).ToList();
|
||||
var gap1 = sorted[1].X - (sorted[0].X + sorted[0].Width);
|
||||
var gap2 = sorted[2].X - (sorted[1].X + sorted[1].Width);
|
||||
Check("가로 간격 균등", Math.Abs(gap1 - gap2) <= 1, $"gap1={gap1} gap2={gap2}");
|
||||
|
||||
designer.AdjustSpacing("HorizConcat");
|
||||
sorted = arrangeTargets.OrderBy(c => c.X).ToList();
|
||||
Check("가로 붙이기: 간격 0", sorted[1].X == sorted[0].X + sorted[0].Width
|
||||
&& sorted[2].X == sorted[1].X + sorted[1].Width);
|
||||
|
||||
designer.CenterInPage("H");
|
||||
var bboxAfterCenter = designer.SelectionWorldBounds()!.Value;
|
||||
var pageW = designer.Pages[0].WidthDip;
|
||||
Check("페이지 가로 가운데", Math.Abs((bboxAfterCenter.X + bboxAfterCenter.Width / 2) - pageW / 2) <= 1,
|
||||
$"중심 {bboxAfterCenter.X + bboxAfterCenter.Width / 2} 기대 {pageW / 2}");
|
||||
|
||||
var beforeCut = designer.Pages[0].Controls.Count;
|
||||
designer.Selection.Set(new[] { arrangeTargets[0] }, arrangeTargets[0]);
|
||||
designer.CutSelection();
|
||||
Check("잘라내기: 삭제됨", designer.Pages[0].Controls.Count == beforeCut - 1);
|
||||
designer.Paste();
|
||||
Check("잘라내기 → 붙여넣기 복원", designer.Pages[0].Controls.Count == beforeCut);
|
||||
|
||||
// 남은 배치 테스트 컨트롤 정리
|
||||
designer.Selection.Set(designer.Pages[0].Controls.Where(c => c.Y >= 480).ToList());
|
||||
designer.DeleteSelection();
|
||||
|
||||
// 20-5) 탭순서 편집 — 모드 진입 → 역순 클릭 → 완료 적용(레거시 규약 0,100,200…)
|
||||
designer.AddControlAt("TextBox", new Point(100, 620));
|
||||
designer.AddControlAt("TextBox", new Point(300, 620));
|
||||
designer.AddControlAt("TextBox", new Point(500, 620));
|
||||
var tabTargets = designer.Pages[0].Controls
|
||||
.Where(c => c.Type == "TextBox" && c.Y >= 600)
|
||||
.OrderBy(c => c.X)
|
||||
.ToList();
|
||||
|
||||
designer.ToggleTabOrderMode(); // 시작
|
||||
Check("탭순서: 모드 진입 + 배지 생성", designer.IsTabOrderMode
|
||||
&& designer.Overlay.TabBadges.Count >= 3,
|
||||
$"배지 {designer.Overlay.TabBadges.Count}");
|
||||
|
||||
// 오른쪽→왼쪽 역순으로 클릭(입력 순서 지정)
|
||||
for (var i = tabTargets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var world = designer.WorldBoundsOf(tabTargets[i]);
|
||||
designer.Interaction.PointerDown(new Point(world.X + world.Width / 2, world.Y + world.Height / 2), none);
|
||||
designer.Interaction.PointerUp(new Point(world.X + world.Width / 2, world.Y + world.Height / 2), none);
|
||||
}
|
||||
Check("탭순서: 배지 순번 표시", designer.Overlay.TabBadges.Count(b => b.IsAssigned) == 3);
|
||||
|
||||
designer.ToggleTabOrderMode(); // 완료(적용)
|
||||
Check("탭순서: 적용 — 클릭 순서 0/100/200", !designer.IsTabOrderMode
|
||||
&& tabTargets[2].Model.Props.GetText("TabIndex") == "0"
|
||||
&& tabTargets[1].Model.Props.GetText("TabIndex") == "100"
|
||||
&& tabTargets[0].Model.Props.GetText("TabIndex") == "200",
|
||||
$"실제 {tabTargets[2].Model.Props.GetText("TabIndex")}/{tabTargets[1].Model.Props.GetText("TabIndex")}/{tabTargets[0].Model.Props.GetText("TabIndex")}");
|
||||
Check("탭순서: 미클릭 컨트롤 뒤 순번(999999+)",
|
||||
Find("Label").Model.Props.GetText("TabIndex")?.StartsWith("1000") == true);
|
||||
designer.Undo.Undo();
|
||||
Check("탭순서: Undo 원복",
|
||||
designer.Pages[0].Controls.First(c => c.Type == "TextBox" && c.Y >= 600).Model.Props.GetText("TabIndex") is null or "0");
|
||||
|
||||
// 정리
|
||||
designer.Selection.Set(designer.Pages[0].Controls.Where(c => c.Y >= 600).ToList());
|
||||
designer.DeleteSelection();
|
||||
|
||||
// 20-6) 전체 속성 그리드(고급) — 토글 → raw 편집 → 속성 추가
|
||||
var advTarget = Find("TextBox");
|
||||
designer.Selection.SetSingle(advTarget);
|
||||
var toggleRow = designer.Inspector.Rows
|
||||
.OfType<ViewModels.Inspector.ToggleAdvancedRowViewModel>().FirstOrDefault();
|
||||
Check("고급: 토글 행 존재", toggleRow is not null);
|
||||
toggleRow!.ToggleCommand!.Execute(null, EventArgs.Empty);
|
||||
var advRows = designer.Inspector.Rows
|
||||
.OfType<ViewModels.Inspector.TextRowViewModel>()
|
||||
.Where(r => r.Label is "LocationOnBase" or "TabIndex" or "DisplaySequence")
|
||||
.ToList();
|
||||
Check("고급: raw 속성 행 노출", designer.Inspector.Rows
|
||||
.OfType<ViewModels.Inspector.SectionRowViewModel>()
|
||||
.Any(r => r.Label.StartsWith("전체 속성")));
|
||||
|
||||
var addRow = designer.Inspector.Rows
|
||||
.OfType<ViewModels.Inspector.AddPropertyRowViewModel>().FirstOrDefault();
|
||||
Check("고급: 속성 추가 행 존재", addRow is not null);
|
||||
addRow!.KeyText = "EnterTabYon";
|
||||
addRow.AddCommand!.Execute(null, EventArgs.Empty);
|
||||
Check("고급: 속성 추가됨", advTarget.Model.Props.Contains("EnterTabYon"));
|
||||
var enterTabRow = designer.Inspector.Rows
|
||||
.OfType<ViewModels.Inspector.TextRowViewModel>()
|
||||
.FirstOrDefault(r => r.Label == "EnterTabYon");
|
||||
Check("고급: 추가 속성 행 노출", enterTabRow is not null);
|
||||
enterTabRow!.ValueText = "True";
|
||||
Check("고급: raw 값 커밋", advTarget.Model.Props.GetText("EnterTabYon") == "True");
|
||||
|
||||
// 21) JSON 왕복(부 포맷) — 편집 결과 유지
|
||||
var jsonSerializer = new Core.Serialization.FormJsonSerializer();
|
||||
var json = jsonSerializer.Write(designer.Document);
|
||||
var fromJson = jsonSerializer.Read(json);
|
||||
Check("JSON 왕복 — XML 동일", serializer.Write(fromJson) == serializer.Write(designer.Document));
|
||||
|
||||
// 22) 연속 Undo 로 빈 문서까지
|
||||
var guard = 0;
|
||||
while (designer.Undo.CanUndo && guard++ < 80)
|
||||
{
|
||||
designer.Undo.Undo();
|
||||
}
|
||||
Check("전체 Undo → 빈 문서", designer.Document.Pages[0].Controls.Count == 0
|
||||
&& designer.Document.Pages.Count == 1,
|
||||
$"실제 컨트롤 {designer.Document.Pages[0].Controls.Count} 페이지 {designer.Document.Pages.Count}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failed++;
|
||||
lines.Add("EXCEPTION " + ex);
|
||||
}
|
||||
|
||||
lines.Add($"결과: 실패 {failed}건");
|
||||
File.WriteAllText(reportPath, string.Join(Environment.NewLine, lines));
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user