using Microsoft.VisualStudio.TestTools.UnitTesting;
using SheetMe.Core.Catalog;
using SheetMe.Core.Models;
namespace SheetMe.Core.Tests;
///
/// 여러 컨트롤을 함께 고쳤을 때 레거시가 못 여는 XML이 나오지 않는지 고정한다.
///
/// 여기서 막는 것들은 전부 화면으로 확인할 수 없는 손상이다 —
/// 디자이너는 멀쩡해 보이고, EMR 에서만 다르게 나오거나 서식이 아예 안 열린다.
///
[TestClass]
public class BatchEditPolicyTests
{
#region Helpers
private static ControlElement Element(string type, string id = "c")
=> new() { Id = id, Type = type };
private static IReadOnlyList KeysOf(IReadOnlyList types)
=> BatchEditPolicy.MergeCommonDefs(types).Select(d => d.Key).ToList();
#endregion
#region Tests - 공통 속성 병합
[TestMethod]
public void 타입이_하나면_그_타입의_속성을_그대로_준다()
{
var label = ControlRegistry.Find("Label");
Assert.IsNotNull(label);
CollectionAssert.AreEqual(
label.Properties.Select(d => d.Key).ToList(),
KeysOf(new[] { "Label" }).ToList());
}
[TestMethod]
public void 정렬은_선택지가_달라_공통에_오지_않는다()
{
// 라벨은 ContentAlignment 9값(TopLeft…), 텍스트박스는 HorizontalAlignment 3값(Left/Center/Right).
// 키 이름만 비교하는 구현으로 되돌아가면 "MiddleCenter" 가 텍스트박스에 들어가고,
// 그 값은 레거시가 읽지 못한다.
CollectionAssert.DoesNotContain(KeysOf(new[] { "Label", "TextBox" }).ToList(), "TextAlign");
}
[TestMethod]
public void 표시_키는_대소문자로_갈린다()
{
// 라벨만 소문자 visible 이다(레거시가 그림자 속성을 쓴다). 별칭으로 묶으면
// 라벨에 대문자 키가 생겨 EMR 에서는 사라지는데 우리 미리보기는 계속 보여 준다.
var mixed = KeysOf(new[] { "Label", "TextBox" }).ToList();
CollectionAssert.DoesNotContain(mixed, "visible");
CollectionAssert.DoesNotContain(mixed, "Visible");
}
[TestMethod]
public void 스키마를_모르는_타입이_섞이면_공통이_없다()
{
// 무엇이 공통인지 말할 근거가 없다 — 추측해서 열면 그게 손상 경로가 된다
Assert.AreEqual(0, KeysOf(new[] { "Label", "Spread" }).Count);
}
[TestMethod]
public void 같은_정의를_가진_공통_속성은_병합된다()
{
// 공통 7종은 `=> new()` 식 본문 프로퍼티라 접근할 때마다 새 인스턴스다.
// def 참조를 비교하는 구현이면 전부 '다르다'가 나와 이 검사가 깨진다.
CollectionAssert.Contains(KeysOf(new[] { "Label", "TextBox" }).ToList(), "PrintOutPut");
}
[TestMethod]
public void 선택이_없으면_빈_집합이다()
{
Assert.AreEqual(0, BatchEditPolicy.MergeCommonDefs(Array.Empty()).Count);
}
[TestMethod]
public void 공통에_못_올린_것의_개수를_센다()
{
// 조용히 빼면 사용자는 그 속성이 존재하지 않는다고 읽는다 — 개수를 알려 줘야 한다
Assert.IsTrue(BatchEditPolicy.ExcludedCount(new[] { "Label", "TextBox" }) > 0);
Assert.AreEqual(0, BatchEditPolicy.ExcludedCount(new[] { "Label" }));
}
#endregion
#region Tests - 차단
[TestMethod]
public void 대상이_하나면_아무것도_막지_않는다()
{
// 이 기능이 기존 단일 선택 동작을 좁히면 안 된다
var one = new[] { Element("Label") };
foreach (var key in new[] { "Name", "SignatureIndex", "Formula", "SequenceCode", "TextAlign" })
{
Assert.IsTrue(BatchEditPolicy.IsBatchable(key, one).Allowed, key);
}
}
[TestMethod]
public void 유일해야_하는_키는_함께_못_바꾼다()
{
var two = new[] { Element("Label", "a"), Element("Label", "b") };
foreach (var key in new[] { "Name", "SignatureIndex", "DisplaySequence", "TabIndex", "LinkToNewDesign" })
{
Assert.IsFalse(BatchEditPolicy.IsBatchable(key, two).Allowed, key);
}
}
[TestMethod]
public void 남을_이름으로_가리키는_배선은_함께_못_바꾼다()
{
var two = new[] { Element("RadioButton", "a"), Element("RadioButton", "b") };
foreach (var key in new[] { "Formula", "DataActionTagControl", "AssociatedControl", "EventHandlerMappingTag" })
{
Assert.IsFalse(BatchEditPolicy.IsBatchable(key, two).Allowed, key);
}
// 액션 태그 자체는 정상 작업이다 — 같은 문항의 라디오에 같은 액션이 붙는다
Assert.IsTrue(BatchEditPolicy.IsBatchable("DataActionTag", two).Allowed);
}
[TestMethod]
public void 일련번호_발급은_함께_못_바꾼다()
{
// 서식을 여는 것만으로 DB 카운터가 오른다 — Undo 로 덮이지 않는 유일한 부류다
var two = new[] { Element("Label", "a"), Element("Label", "b") };
foreach (var key in new[] { "SequenceCode", "SequenceType", "SequenceNumber", "CheckSequenceAtSaving" })
{
Assert.IsFalse(BatchEditPolicy.IsBatchable(key, two).Allowed, key);
}
}
[TestMethod]
public void 정렬은_타입이_섞였을_때만_막는다()
{
var sameType = new[] { Element("TextBox", "a"), Element("TextBox", "b") };
var mixedType = new[] { Element("Label", "a"), Element("TextBox", "b") };
Assert.IsTrue(BatchEditPolicy.IsBatchable("TextAlign", sameType).Allowed);
Assert.IsFalse(BatchEditPolicy.IsBatchable("TextAlign", mixedType).Allowed);
}
[TestMethod]
public void 선_방향은_원문_경로에서만_막는다()
{
// 전용 편집기는 컨트롤마다 크기까지 함께 고치므로(ApplyLineChange) 다중 선택에서도 옳다.
// 원문 텍스트 행은 속성만 써서 XML 은 Vertical 인데 경계는 가로인 선을 만든다.
var lines = new[] { Element("Line", "a"), Element("Line", "b") };
Assert.IsTrue(BatchEditPolicy.IsBatchable("Orientation", lines).Allowed);
Assert.IsFalse(BatchEditPolicy.IsBatchable("Orientation", lines, viaAdvancedRaw: true).Allowed);
Assert.IsFalse(BatchEditPolicy.IsBatchable("BorderWidth", lines, viaAdvancedRaw: true).Allowed);
}
[TestMethod]
public void 같은_묶음의_라디오는_기본_선택을_함께_못_바꾼다()
{
// 레거시 라디오는 GroupName 이 없어 직계 부모가 문항이다. 둘 다 켜면 한 문항의 답이 둘이 되고,
// 로더가 부모에 붙이기 전에 값을 대입하므로 열 때 정리되지도 않는다.
var parent = Element("Panel", "p");
var first = Element("RadioButton", "r1");
var second = Element("RadioButton", "r2");
var targets = new[] { first, second };
Assert.IsFalse(BatchEditPolicy.IsBatchable("Checked", targets, parentOf: _ => parent).Allowed);
}
[TestMethod]
public void 부모가_다른_라디오는_함께_바꿀_수_있다()
{
var left = Element("Panel", "p1");
var right = Element("Panel", "p2");
var first = Element("RadioButton", "r1");
var second = Element("RadioButton", "r2");
var allowed = BatchEditPolicy.IsBatchable("Checked", new[] { first, second },
parentOf: element => ReferenceEquals(element, first) ? left : right).Allowed;
Assert.IsTrue(allowed);
}
[TestMethod]
public void 체크박스는_배타가_없어_함께_바꿀_수_있다()
{
var parent = Element("Panel", "p");
var targets = new[] { Element("CheckBox", "c1"), Element("CheckBox", "c2") };
Assert.IsTrue(BatchEditPolicy.IsBatchable("Checked", targets, parentOf: _ => parent).Allowed);
}
[TestMethod]
public void 부모를_모르면_라디오를_막는다()
{
// 모르면 막는 쪽이다 — 여기서 틀리면 한 문항에 답이 둘 켜진 서식이 나가고
// 그것은 화면으로 확인할 수 없다
var targets = new[] { Element("RadioButton", "r1"), Element("RadioButton", "r2") };
Assert.IsFalse(BatchEditPolicy.IsBatchable("Checked", targets).Allowed);
}
#endregion
#region Tests - 고급(원문) 경로
[TestMethod]
public void 전부_갖고_전부_문자열일_때만_원문을_연다()
{
var first = Element("Label", "a");
var second = Element("Label", "b");
first.Props.SetText("Tooltip", "가");
second.Props.SetText("Tooltip", "나");
Assert.IsTrue(BatchEditPolicy.AllShareTextShape("Tooltip", new[] { first, second }));
}
[TestMethod]
public void 값의_형태가_다르면_원문을_열지_않는다()
{
// 이미지 원문(BinaryValue)을 문자열로 덮으면 복구할 수 없다.
// GetText 가 문자열 아닌 값에 null 을 주므로 빈 값과 이미지가 둘 다 "" 로 접혀
// 갈렸다는 표시조차 안 나온다 — 그래서 형태로 판정한다.
var text = Element("PictureBox", "a");
var binary = Element("PictureBox", "b");
text.Props.SetText("Image", string.Empty);
binary.Props.Set("Image", new LegacyPropValue.BinaryValue { Base64 = "AQID" });
// 어느 쪽을 먼저 골랐든 결과가 같아야 한다 — 클릭 순서로 안전성이 갈리면 안 된다
Assert.IsFalse(BatchEditPolicy.AllShareTextShape("Image", new[] { text, binary }));
Assert.IsFalse(BatchEditPolicy.AllShareTextShape("Image", new[] { binary, text }));
}
[TestMethod]
public void 일부만_가진_키는_원문을_열지_않는다()
{
// 없던 대상에 키가 생기면 레거시 서식생성기가 열 때마다 오류 모달을 띄운다
var has = Element("Label", "a");
var lacks = Element("Label", "b");
has.Props.SetText("Score", "5");
Assert.IsFalse(BatchEditPolicy.AllShareTextShape("Score", new[] { has, lacks }));
Assert.AreEqual(1, BatchEditPolicy.CountHaving("Score", new[] { has, lacks }));
}
#endregion
}