표(Spread) 이름 변경·복제 차단 — 레거시에서 서식이 통째로 안 열리는 경로 봉쇄

Spread 격자는 E_SpdMst 에서 SpdShtCod+SpdName 으로만 조회되는데 SheetMe 에는 그 행을
만드는 경로가 없다(SELECT 3곳뿐). 이름이 바뀌거나 복제본이 생기면 조회 0건 →
Spread.vb:922-955 가 0바이트 파일을 만들고 FpSpread.Open 이 예외 →
bzDesignSheetLoader.LoadObject 에는 try/catch 가 없어 재귀를 타고 :425-427 로 전파되어
Return Nothing. 즉 표가 사라지는 수준이 아니라 그 서식이 아예 열리지 않는다.

- 이름 변경: 완전 금지(RenameControl 진입부 가드). 기존 중복 이름 거부 경로도 Rebuild() 를
  호출하도록 통일 — 지금까지는 return 만 해서 편집 상자에 잘못된 이름이 남아 있었다.
- 붙여넣기: 표만 제외하고 나머지는 통과 + 제외 건수 알림. 전체 거부는 라벨 20개에 표 1개가
  섞인 복사 같은 정상 작업까지 막는다. 조기 반환을 Undo.Snapshot() 앞에 둬 빈 스텝이
  쌓이지 않게 했다.
- 가드는 커맨드 람다가 아니라 Paste() 메서드 본문에 — CanvasKeyboardBehavior 가 커맨드를
  우회해 designer.CopySelection()/Paste() 를 직접 호출하므로 키보드 경로도 함께 막힌다.
- 잘라내기는 별도 가드 불필요(표 삭제 자체는 안전), 복제는 Copy+Paste 라 자동 차단.
- 판정은 VM 형이 아니라 모델 Type 문자열 + 자식 재귀 — 클립보드가 ControlElement 이고
  컨테이너에 중첩된 표도 잡아야 한다.

검증: edit-smoke 에 판정·필터 순수 로직 8건 추가(FilterPastable 을 알림과 분리해 무인
검증이 가능하도록 함). 실 UI 프로브로 P062(Spread 실물 보유)를 열어 실제 입력으로 이름
변경을 시도 → 가드 대화상자 발동, 레이어·인스펙터 헤더 모두 Spread1 유지, 확인 후 편집
상자 원복까지 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-11 17:45:53 +09:00
co-authored by Claude Fable 5
parent d0538ef95a
commit 3dcac31a5b
3 changed files with 85 additions and 4 deletions
+26 -2
View File
@@ -384,7 +384,31 @@ public static class EditSmoke
var fromJson = jsonSerializer.Read(json);
Check("JSON 왕복 — XML 동일", serializer.Write(fromJson) == serializer.Write(designer.Document));
// 22) 쿼리 편집기 — XAML 로드/바인딩과 두 모드(SQL/상용구)를 실제로 생성해 확인
// 22) Spread 판정 — 복제/이름변경 가드의 근거 술어(가드 자체는 MessageBox 를 띄우므로 무인 검증 불가)
var spreadElement = new Core.Models.ControlElement { Type = "Spread", Id = "Spread1" };
var panelWithSpread = new Core.Models.ControlElement { Type = "Panel", Id = "Panel9" };
panelWithSpread.Children.Add(new Core.Models.ControlElement { Type = "Label", Id = "L9" });
panelWithSpread.Children.Add(spreadElement.Clone());
var plainPanel = new Core.Models.ControlElement { Type = "Panel", Id = "Panel8" };
plainPanel.Children.Add(new Core.Models.ControlElement { Type = "Label", Id = "L8" });
Check("Spread 판정: 자신", DesignerViewModel.ContainsSpread(spreadElement));
Check("Spread 판정: 컨테이너 자손", DesignerViewModel.ContainsSpread(panelWithSpread));
Check("Spread 판정: 무관 컨트롤 false", !DesignerViewModel.ContainsSpread(plainPanel));
Check("Spread 판정: Placeholder 오탐 없음",
!DesignerViewModel.ContainsSpread(new Core.Models.ControlElement { Type = "Placeholder", Id = "P1" }));
// 붙여넣기 필터 — 표만 걸러내고 나머지는 통과시켜야 한다(전체 거부는 정상 작업까지 막는다)
var mixed = new[] { plainPanel, spreadElement, panelWithSpread,
new Core.Models.ControlElement { Type = "Label", Id = "L7" } };
var (pastable, blockedSpreads) = DesignerViewModel.FilterPastable(mixed);
Check("붙여넣기 필터: 표 2개 차단", blockedSpreads == 2, $"차단 {blockedSpreads}");
Check("붙여넣기 필터: 나머지 2개 통과", pastable.Count == 2, $"통과 {pastable.Count}");
Check("붙여넣기 필터: 통과분에 표 없음", pastable.All(e => !DesignerViewModel.ContainsSpread(e)));
Check("붙여넣기 필터: 표 없으면 전량 통과",
DesignerViewModel.FilterPastable(new[] { plainPanel }) is { BlockedSpreads: 0, Pastable.Count: 1 });
// 23) 쿼리 편집기 — XAML 로드/바인딩과 두 모드(SQL/상용구)를 실제로 생성해 확인
var sqlEditor = new Views.QueryEditorWindow("스모크", "SELECT 1 FROM DUAL");
var variableItems = (sqlEditor.FindName("VariableList") as System.Windows.Controls.ListBox)?
.ItemsSource?.Cast<object>().Count() ?? 0;
@@ -401,7 +425,7 @@ public static class EditSmoke
Check("상용구 모드: 제목에 '쿼리' 없음", !wordEditor.Title.Contains("쿼리"), wordEditor.Title);
wordEditor.Close();
// 23) 연속 Undo 로 빈 문서까지
// 24) 연속 Undo 로 빈 문서까지
var guard = 0;
while (designer.Undo.CanUndo && guard++ < 80)
{