{ ["find"] = "Ctrl+T" }); // newtab 과 충돌
var conflicts = sm.FindConflicts();
Assert.Contains(conflicts, g => g.Count == 2 &&
g.Any(b => b.Command == "newtab") && g.Any(b => b.Command == "find"));
Assert.Empty(new ShortcutManager().FindConflicts()); // 기본 표에는 충돌 없음
}
[Theory]
[InlineData("Ctrl+Shift+P", System.Windows.Input.ModifierKeys.Control | System.Windows.Input.ModifierKeys.Shift, System.Windows.Input.Key.P)]
[InlineData("F12", System.Windows.Input.ModifierKeys.None, System.Windows.Input.Key.F12)]
public void ShortcutManager_Parse_조합문자열(string gesture, System.Windows.Input.ModifierKeys mods, System.Windows.Input.Key key)
{
var parsed = ShortcutManager.Parse(gesture);
Assert.NotNull(parsed);
Assert.Equal(mods, parsed!.Value.Mods);
Assert.Equal(key, parsed.Value.Key);
Assert.Null(ShortcutManager.Parse("Not+A+Key"));
Assert.Null(ShortcutManager.Parse(""));
}
[Fact]
public void ShortcutManager_Normalize_순서무관_동일취급()
=> Assert.Equal(ShortcutManager.Normalize("Ctrl+Shift+P"), ShortcutManager.Normalize("shift+p+ctrl"));
// ---------- §I 즐겨찾기 ----------
[Fact]
public void 북마크_Netscape_HTML_파싱_및_역호환()
{
var html = """
- 사이트 <A>
- Plain
""";
var parsed = BookmarkManagerWindow.ParseNetscapeBookmarks(html).ToList();
Assert.Equal(2, parsed.Count);
Assert.Equal("https://a.example.com/x?a=1&b=2", parsed[0].Url);
Assert.Equal("사이트 ", parsed[0].Title);
Assert.Equal("Plain", parsed[1].Title);
}
[Fact]
public void Bookmark_레코드_Folder_기존_JSON_역직렬화_호환()
{
// 기존 저장 파일(2필드)이 Folder 기본값(null)으로 읽히는지
var old = System.Text.Json.JsonSerializer.Deserialize("""{"Title":"T","Url":"https://x"}""");
Assert.NotNull(old);
Assert.Null(old!.Folder);
var @new = new Bookmark("T", "https://x", "작업");
Assert.Equal("작업", @new.Folder);
}
// ---------- §A 테마 ----------
[Theory]
[InlineData("dark")]
[InlineData("light")]
[InlineData("highcontrast")]
public void ThemeResolver_고정값_매핑(string setting)
{
var (theme, custom) = ThemeResolver.Resolve(setting);
Assert.Equal(setting, theme);
Assert.Null(custom);
}
[Fact]
public void ThemeResolver_system_은_dark_또는_light_로_확정()
{
var (theme, _) = ThemeResolver.Resolve("system");
Assert.Contains(theme, new[] { "dark", "light" }); // 레지스트리 결과에 따라
}
[Fact]
public void ThemeResolver_커스텀_경로_우선()
{
var file = Path.GetTempFileName();
try
{
var (theme, custom) = ThemeResolver.Resolve("whatever", file);
Assert.Equal("custom", theme);
Assert.Equal(file, custom);
// 없는 파일이면 다크 폴백
Assert.Equal("dark", ThemeResolver.Resolve("whatever", @"Z:\nope.xaml").Theme);
}
finally { try { File.Delete(file); } catch { } }
}
/// Dark/Light/HighContrast 세 테마가 동일한 키 집합을 정의하는지 —
/// 한 테마에만 있는 키는 전환 시 MissingKey 런타임 예외로 이어진다.
[Fact]
public void 세_테마_사전의_키_집합이_동일하다()
{
var keysOf = (string xaml) =>
System.Text.RegularExpressions.Regex.Matches(xaml, "x:Key=\"([^\"]+)\"")
.Select(m => m.Groups[1].Value).ToHashSet();
var dark = keysOf(SourceTree.ThemeDarkXaml);
var light = keysOf(SourceTree.ThemeLightXaml);
var hc = keysOf(File.ReadAllText(SourceTree.AppPath("Themes/HighContrast.xaml")));
Assert.True(dark.SetEquals(light), $"Dark/Light 키 불일치: dark-only={string.Join(",", dark.Except(light))} light-only={string.Join(",", light.Except(dark))}");
Assert.True(dark.SetEquals(hc), $"Dark/HighContrast 키 불일치: dark-only={string.Join(",", dark.Except(hc))} hc-only={string.Join(",", hc.Except(dark))}");
}
// ---------- §F 설정 ----------
[Fact]
public void ResetToDefaults_원격토큰은_유지_나머지는_초기화()
{
var c = new AppConfig
{
RemoteAccessToken = "keep-me",
Concurrency = 1,
Theme = "light",
ToastDurationMs = 100,
DownloadSortOrder = "title",
};
c.ResetToDefaults();
Assert.Equal("keep-me", c.RemoteAccessToken); // 기기 페어링 유지
Assert.Equal(new AppConfig().Concurrency, c.Concurrency);
Assert.Equal("dark", c.Theme);
Assert.Equal(4000, c.ToastDurationMs);
Assert.Equal("newest", c.DownloadSortOrder);
}
}