using System.Text.RegularExpressions; namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests; public sealed class LegacyWorkspaceLayoutContractTests { private static readonly string RepositoryRoot = FindRepositoryRoot(); private static readonly string WebRoot = Path.Combine( RepositoryRoot, "src", "MBN_STOCK_WEBVIEW.LegacyParityApp", "Web"); private static readonly string Markup = File.ReadAllText(Path.Combine(WebRoot, "index.html")); private static readonly string Styles = File.ReadAllText(Path.Combine(WebRoot, "styles.css")); private static readonly string App = File.ReadAllText(Path.Combine(WebRoot, "app.js")); private static readonly string Navigation = File.ReadAllText( Path.Combine(WebRoot, "workspace-navigation.js")); [Fact] public void ShellUsesSwitchableWorkspaceBesidePersistentSchedule() { var workspaceStart = Markup.IndexOf("id=\"workspace-region\"", StringComparison.Ordinal); var stockStart = Markup.IndexOf("id=\"stock-workspace\"", StringComparison.Ordinal); var catalogStart = Markup.IndexOf("id=\"catalog-workspace\"", StringComparison.Ordinal); var scheduleStart = Markup.IndexOf("class=\"playlist-panel\"", StringComparison.Ordinal); Assert.True(workspaceStart >= 0); Assert.True(stockStart > workspaceStart); Assert.True(catalogStart > stockStart); Assert.True(scheduleStart > catalogStart); Assert.Matches( @"<\/section>\s*<\/div>\s*<\/section>\s*<\/section>\s*
]*hidden inert", Markup); Assert.Contains("항상 표시

송출 스케줄

", Markup, StringComparison.Ordinal); } [Fact] public void WorkAndScheduleUseResponsiveTwoToOneColumns() { Assert.Contains( "grid-template-columns: minmax(900px, 2fr) minmax(600px, 1fr);", Styles, StringComparison.Ordinal); Assert.Matches( new Regex( @"\.playlist-toolbar\s*\{[^}]*flex-wrap:\s*wrap", RegexOptions.CultureInvariant), Styles); Assert.Matches( new Regex( @"\.playlist-row\s*\{[^}]*min-width:\s*600px", RegexOptions.CultureInvariant), Styles); } [Fact] public void ModernOperatorThemePreservesAccessibleAndSemanticStates() { Assert.Contains( "Modern operator theme: visual-only overrides; geometry and native contracts stay intact", Styles, StringComparison.Ordinal); Assert.Contains("--ui-canvas:", Styles, StringComparison.Ordinal); Assert.Contains("--ui-accent:", Styles, StringComparison.Ordinal); Assert.Contains("--ui-success:", Styles, StringComparison.Ordinal); Assert.Contains("--ui-danger:", Styles, StringComparison.Ordinal); Assert.Contains( "font-family: \"Segoe UI Variable\", \"Segoe UI\", \"Noto Sans KR\"", Styles, StringComparison.Ordinal); Assert.Matches( new Regex( @"button:focus-visible,[\s\S]*?outline:\s*2px solid var\(--ui-accent\)", RegexOptions.CultureInvariant), Styles); Assert.Contains(".playlist-data-row.on-air.selected", Styles, StringComparison.Ordinal); Assert.Contains(".playout-actions .take-in:not(:disabled)", Styles, StringComparison.Ordinal); Assert.Contains(".playout-actions .next:not(:disabled)", Styles, StringComparison.Ordinal); Assert.Contains(".playout-actions .take-out:not(:disabled)", Styles, StringComparison.Ordinal); Assert.Contains( ".playlist-data-row[data-active=\"true\"]:not(.on-air)", Styles, StringComparison.Ordinal); Assert.Contains( ".playlist-data-row[aria-selected=\"true\"]:not(.on-air)", Styles, StringComparison.Ordinal); Assert.Contains("@media (forced-colors: active)", Styles, StringComparison.Ordinal); Assert.Contains("scrollbar-gutter: stable", Styles, StringComparison.Ordinal); Assert.Contains("@media (prefers-reduced-motion: reduce)", Styles, StringComparison.Ordinal); Assert.Contains("aria-keyshortcuts=\"F8\"", Markup, StringComparison.Ordinal); Assert.Contains("aria-keyshortcuts=\"Escape\"", Markup, StringComparison.Ordinal); Assert.Contains( ".connection-state.dry-run:not(.outcome-unknown)", Styles, StringComparison.Ordinal); Assert.Contains("accessibleStates.push(\"현재 PGM\")", App, StringComparison.Ordinal); Assert.Contains("accessibleStates.push(\"송출 후보\")", App, StringComparison.Ordinal); Assert.Contains("accessibleStates.push(\"삭제 선택됨\")", App, StringComparison.Ordinal); Assert.Matches( new Regex( @"id=""stock-search-button""[\s\S]*?]*viewBox=""0 0 24 24""", RegexOptions.CultureInvariant), Markup); Assert.Matches( new Regex( @"class=""playlist-heading-icon""[\s\S]*?]*viewBox=""0 0 24 24""", RegexOptions.CultureInvariant), Markup); } [Fact] public void NavigationChangesPresentationWithoutNativeOrStoredState() { Assert.Contains("entry.view.hidden = !isCurrent", Navigation, StringComparison.Ordinal); Assert.Contains( "entry.view.toggleAttribute(\"inert\", !isCurrent)", Navigation, StringComparison.Ordinal); Assert.Contains("nav-collapsed", Navigation, StringComparison.Ordinal); Assert.DoesNotContain("postMessage", Navigation, StringComparison.Ordinal); Assert.DoesNotContain("localStorage", Navigation, StringComparison.Ordinal); Assert.DoesNotContain("sessionStorage", Navigation, StringComparison.Ordinal); } private static string FindRepositoryRoot() { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null) { if (File.Exists(Path.Combine(directory.FullName, "MBN_STOCK_WEBVIEW.sln"))) { return directory.FullName; } directory = directory.Parent; } throw new DirectoryNotFoundException("Repository root could not be located."); } }