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 }