자동완성 팝업 — 소멸 규칙과 위치, 그리고 그걸 재는 진단
두 가지 신고를 고쳤다: 위치가 안 맞고, 항목을 고르기 전까지 사라지지 않는다. ■ 사라지지 않던 문제 StaysOpen=True 로 두고 있었다. 편집 포커스를 잃지 않으려고 그렇게 했는데, 그 값은 <b>바깥을 클릭해도 닫히지 않는다</b>는 뜻이다. False 로 바꿨다. 더 큰 원인은 따로 있었다. 목록 갱신을 TextChanged 에서만 불렀다. 커서만 옮기면(방향키·클릭·다음 줄) 아무 일도 일어나지 않아, 옛 문맥의 목록이 그대로 남았다 — FROM 뒤에서 테이블 목록을 띄운 뒤 다음 줄로 내려가도 그 테이블 목록이 계속 떠 있었다. 스크린샷의 상태가 정확히 이것이다. 이제 SelectionChanged 에서도 판정한다. 목록이 걸린 낱말 범위를 커서가 벗어나면 닫는다. 타이핑에 따른 커서 이동과 순수한 커서 이동을 구분해야 해서(TextChanged 뒤에 SelectionChanged 가 이어서 온다) 타이핑 표시를 Input 우선순위로 내려 두고 판정한다. 포커스를 잃을 때·편집기가 스크롤될 때·창이 비활성될 때도 닫는다. ■ 위치가 안 맞던 문제 셋이 겹쳐 있었다. 1. WPF Popup 은 열려 있는 동안 Placement 를 다시 계산하지 않는다 — 오프셋만 바꾸면 제자리에 머문다. 기준점이 달라졌을 때만 닫았다 다시 연다. 2. GetRectFromCharacterIndex 가 범위 밖에서 Empty 를 준다 — 캐럿 → 원점 순으로 물러선다. 3. 스크롤 위치에 따라 rect 가 편집 영역을 크게 벗어난다. 진단으로 재 보니 200줄짜리 문서에서 세로 오프셋이 3,074px 이었다(편집 높이는 494px). 목록이 창 밖에 떠 있었다는 뜻이다. 편집 영역 안으로 제한했다. 기준점은 커서가 아니라 완성 중인 낱말의 시작이다 — 글자마다 흔들리지 않는다. ■ --query-popup 진단 이 종류는 눈으로만 보면 놓친다(두 번 놓쳤다). 실제 창을 화면 밖에 띄워 7가지를 잰다: 목록이 열리는가 · 같은 낱말을 이어 쳐도 기준점이 그대로인가 · 줄이 바뀌면 내려가는가 · 커서를 옮기면 닫히는가 · 긴 문서에서도 편집 영역 안에 뜨는가 · 포커스를 잃으면 닫히는가 · 바깥 클릭으로 닫히는 설정인가. 현재 7/7. 만들면서 결함이 하나 더 드러났다: 프로그램으로 Text 를 넣으면 TextChanged 시점의 커서가 아직 옛 자리라 사용자가 친 것과 같은 상태가 되지 않는다. 진단용 통로를 코드베이스 관례대로 (RemoveSelectedPageForSmoke 와 같은 형태) 열어 두고 그 자리에서 갱신을 부른다. 회귀: 테스트 216/216, 편집 스모크 실패 0, 팝업 점검 7/7, 검증 실행 점검 10/10, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f7c90979b9
commit
7ce7506286
@@ -615,6 +615,117 @@ public static class DbSmoke
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 자동완성 팝업 동작 점검(--query-popup) — 위치와 소멸 규칙을 실제 창을 띄워 잰다.
|
||||
///
|
||||
/// 이 둘은 눈으로만 보면 놓친다. 팝업이 옛 문맥을 물고 남아 있거나 엉뚱한 자리에 뜨는 문제가
|
||||
/// 실제로 두 번 있었다(커서 이동 시 갱신 안 됨 / 열린 채로는 Placement 재계산 안 됨).
|
||||
/// </summary>
|
||||
public static int RunQueryPopupCheck(string reportPath)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var failures = 0;
|
||||
|
||||
void Check(string label, bool ok, string detail = "")
|
||||
{
|
||||
if (!ok)
|
||||
{
|
||||
failures++;
|
||||
}
|
||||
lines.Add($"{(ok ? "PASS" : "FAIL")} {label}" + (detail.Length > 0 ? $" — {detail}" : string.Empty));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var window = new Views.QueryEditorWindow("점검", string.Empty);
|
||||
// 화면 밖에 띄운다 — 진단이 사용자 화면을 가리지 않게
|
||||
window.WindowStartupLocation = System.Windows.WindowStartupLocation.Manual;
|
||||
window.Left = -10000;
|
||||
window.Top = -10000;
|
||||
window.Show();
|
||||
window.UpdateLayout();
|
||||
|
||||
var box = (System.Windows.Controls.TextBox)window.FindName("SqlBox")!;
|
||||
var popup = (System.Windows.Controls.Primitives.Popup)window.FindName("CompletionPopup")!;
|
||||
|
||||
void Type(string text, int? caret = null)
|
||||
{
|
||||
box.Text = text;
|
||||
box.CaretIndex = caret ?? text.Length;
|
||||
// Text 를 프로그램으로 넣으면 TextChanged 시점의 커서가 아직 옛 자리다 —
|
||||
// 사용자가 친 것과 같은 상태로 맞춘 뒤 갱신을 부른다
|
||||
window.RefreshCompletionForSmoke();
|
||||
DrainDispatcher();
|
||||
}
|
||||
|
||||
// ① FROM 뒤에서 테이블 목록이 뜬다
|
||||
Type("SELECT * FROM P_");
|
||||
Check("FROM 뒤 목록이 열린다", popup.IsOpen);
|
||||
var firstOffset = (popup.HorizontalOffset, popup.VerticalOffset);
|
||||
|
||||
// ② 팝업은 완성 중인 낱말의 시작에 붙는다 — 캐럿을 따라 오른쪽으로 흐르면 안 된다
|
||||
Type("SELECT * FROM P_PATINF");
|
||||
Check("같은 낱말을 이어 쳐도 기준점이 그대로",
|
||||
Math.Abs(popup.HorizontalOffset - firstOffset.HorizontalOffset) < 0.5,
|
||||
$"{firstOffset.HorizontalOffset:F1} → {popup.HorizontalOffset:F1}");
|
||||
|
||||
// ③ 줄이 바뀌면 세로 위치도 따라간다
|
||||
Type("SELECT * FROM P_PATINF\nWHERE PAT");
|
||||
Check("다음 줄에서는 아래로 내려간다",
|
||||
popup.VerticalOffset > firstOffset.VerticalOffset,
|
||||
$"{firstOffset.VerticalOffset:F1} → {popup.VerticalOffset:F1}");
|
||||
|
||||
// ④ 커서만 옮기면 닫힌다 — 옛 문맥의 목록이 남아 있으면 안 된다
|
||||
var wasOpen = popup.IsOpen;
|
||||
box.CaretIndex = 3;
|
||||
DrainDispatcher();
|
||||
Check("커서를 옮기면 닫힌다", wasOpen && !popup.IsOpen);
|
||||
|
||||
// ⑤ 범위를 벗어난 좌표에서도 화면 안에 있어야 한다
|
||||
Type(new string('\n', 200) + "SELECT * FROM P_");
|
||||
// 목록이 편집 영역 밖에 뜨면 화면 구석에 떠 있는 것처럼 보인다
|
||||
Check("긴 문서에서도 편집 영역 안에 뜬다",
|
||||
!double.IsNaN(popup.VerticalOffset) && !double.IsInfinity(popup.VerticalOffset)
|
||||
&& popup.VerticalOffset >= 0 && popup.VerticalOffset <= box.ActualHeight
|
||||
&& popup.HorizontalOffset >= 0 && popup.HorizontalOffset <= box.ActualWidth,
|
||||
$"세로 {popup.VerticalOffset:F1} / 편집 높이 {box.ActualHeight:F1}");
|
||||
|
||||
// ⑥ 포커스를 잃으면 닫힌다
|
||||
Type("SELECT * FROM P_");
|
||||
var openBeforeBlur = popup.IsOpen;
|
||||
System.Windows.Input.Keyboard.ClearFocus();
|
||||
box.RaiseEvent(new System.Windows.RoutedEventArgs(System.Windows.UIElement.LostFocusEvent));
|
||||
DrainDispatcher();
|
||||
Check("포커스를 잃으면 닫힌다", openBeforeBlur && !popup.IsOpen);
|
||||
|
||||
// ⑦ 바깥 클릭으로 닫히는 설정인지
|
||||
Check("바깥 클릭으로 닫히는 설정(StaysOpen=False)", !popup.StaysOpen);
|
||||
|
||||
window.Close();
|
||||
DrainDispatcher();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failures++;
|
||||
lines.Add("예외: " + ex);
|
||||
}
|
||||
|
||||
lines.Add(string.Empty);
|
||||
lines.Add($"결과: 실패 {failures}건");
|
||||
File.WriteAllText(reportPath, string.Join(Environment.NewLine, lines));
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
/// <summary>대기 중인 디스패처 작업을 비운다 — 팝업 갱신이 Input 우선순위로 예약된다</summary>
|
||||
private static void DrainDispatcher()
|
||||
{
|
||||
var frame = new System.Windows.Threading.DispatcherFrame();
|
||||
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
|
||||
System.Windows.Threading.DispatcherPriority.Background,
|
||||
new Action(() => frame.Continue = false));
|
||||
System.Windows.Threading.Dispatcher.PushFrame(frame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 쿼리 검증 실행 점검(--db-trial) — 편집기의 '검증 실행'이 실제 DB 에서 무엇을 돌려주는지.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user