using System.Text.RegularExpressions; namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests; public sealed class LegacyWebContractTests { private static readonly string RepositoryRoot = FindRepositoryRoot(); private static readonly string WebRoot = Path.Combine( RepositoryRoot, "src", "MBN_STOCK_WEBVIEW.LegacyParityApp", "Web"); private static readonly string App = File.ReadAllText(Path.Combine(WebRoot, "app.js")); private static readonly string Markup = File.ReadAllText(Path.Combine(WebRoot, "index.html")); private static readonly string Styles = File.ReadAllText(Path.Combine(WebRoot, "styles.css")); [Fact] public void ShellExposesOriginalStockCutPlaylistAndPlayoutControls() { foreach (var id in new[] { "stock-search", "stock-search-button", "stock-results", "market-tabs", "category-tree", "catalog-expand-all", "moving-average-5", "moving-average-20", "cut-list", "playlist-drop-zone", "playlist-rows", "playlist-move-up", "playlist-move-down", "playlist-delete-selected", "playlist-delete-all", "db-save-button", "db-load-button", "named-playlist-modal", "named-playlist-definitions", "named-playlist-new-title", "named-playlist-create-button", "named-playlist-delete-button", "named-playlist-confirm-button", "legacy-dialog" }) { Assert.Contains($"id=\"{id}\"", Markup, StringComparison.Ordinal); } Assert.Contains("TAKE IN", Markup, StringComparison.Ordinal); Assert.Contains("TAKE OUT", Markup, StringComparison.Ordinal); Assert.Contains("NEXT", Markup, StringComparison.Ordinal); Assert.Matches("class=\"connection-state\"[^>]*>미연결", Markup); Assert.Matches("class=\"active\"[^>]*data-tab-id=\"overseas\">해외", Markup); Assert.Matches("data-tab-id=\"tradingHalt\">정지", Markup); } [Fact] public void WebLayerDoesNotOwnSceneDatabaseIdentityOrPlaylistState() { Assert.DoesNotMatch( new Regex("(?:sceneCode|cutCode|stockCode|dataCode|builderKey)"), App); Assert.DoesNotMatch( new Regex("(?:localStorage|sessionStorage|playlist\\.push|fetch\\s*\\()"), App); Assert.DoesNotMatch( new Regex("\\b(?:5001|N5001|5006|5011|5086|8035|8061|8040|8046|8051|8056|8003|5037)\\b"), App); } [Fact] public void DoubleClickUsesPointerFactsInsteadOfBrowserPolicy() { Assert.Contains("cut-pointer-down", App, StringComparison.Ordinal); Assert.Contains("timestampMilliseconds", App, StringComparison.Ordinal); Assert.Contains("event.ctrlKey", App, StringComparison.Ordinal); Assert.Contains("event.shiftKey", App, StringComparison.Ordinal); var cutFactory = App[ App.IndexOf("function createCutElement", StringComparison.Ordinal)..App.IndexOf("function renderCuts", StringComparison.Ordinal)]; Assert.DoesNotContain("dblclick", cutFactory, StringComparison.Ordinal); } [Fact] public void CutRowsAreReconciledInPlace() { Assert.Contains("dataset.physicalIndex", App, StringComparison.Ordinal); Assert.Contains("function createCutElement", App, StringComparison.Ordinal); Assert.DoesNotContain("cutList.replaceChildren", App, StringComparison.Ordinal); } [Fact] public void StockAndPlaylistRowsPreserveNativeControlFocus() { Assert.Contains("dataset.resultIndex", App, StringComparison.Ordinal); Assert.Contains("dataset.rowId", App, StringComparison.Ordinal); Assert.DoesNotContain("stockResults.replaceChildren", App, StringComparison.Ordinal); Assert.DoesNotContain("playlistRows.replaceChildren", App, StringComparison.Ordinal); } [Fact] public void PlaylistRowsSendSelectionAndMutationIntentsOnly() { foreach (var intent in new[] { "select-playlist-row", "set-playlist-enabled", "reorder-playlist-rows", "move-selected-playlist-rows", "delete-selected-playlist-rows", "delete-all-playlist-rows" }) { Assert.Contains($"send(\"{intent}\"", App, StringComparison.Ordinal); } Assert.Contains("control: event.ctrlKey", App, StringComparison.Ordinal); Assert.Contains("shift: event.shiftKey", App, StringComparison.Ordinal); Assert.Contains("event.key === \"Delete\"", App, StringComparison.Ordinal); Assert.Contains("row.isSelected", App, StringComparison.Ordinal); Assert.Contains("enabled.disabled = state.isBusy === true", App, StringComparison.Ordinal); Assert.Contains("event.target.closest(\"input, button\")", App, StringComparison.Ordinal); Assert.Contains("preserveSelectedBlock", App, StringComparison.Ordinal); Assert.Contains("deferredPlaylistPlainSelectionRowId", App, StringComparison.Ordinal); Assert.Contains("event.dataTransfer.setData(playlistDragMime, rowId)", App, StringComparison.Ordinal); Assert.Contains(".playlist-data-row.drag-before", Styles, StringComparison.Ordinal); Assert.Contains(".playlist-data-row.drag-after", Styles, StringComparison.Ordinal); } [Fact] public void PlaylistReorderUsesKeyedDomNodesAndPreservesFocus() { Assert.Contains("const existingRows = new Map()", App, StringComparison.Ordinal); Assert.Contains("existingRows.get(row.rowId)", App, StringComparison.Ordinal); Assert.Contains("playlistRows.insertBefore(element, elementAtPosition)", App, StringComparison.Ordinal); Assert.Contains("const retainedRowIds = new Set", App, StringComparison.Ordinal); Assert.DoesNotContain("playlistRows.replaceChild", App, StringComparison.Ordinal); } [Fact] public void MarketTabDragSwapsOnHoverThroughExpectedCSharpPositions() { Assert.Contains("send(\"hover-swap-tabs\"", App, StringComparison.Ordinal); Assert.Contains("expectedSourceIndex: sourceIndex", App, StringComparison.Ordinal); Assert.Contains("expectedTargetIndex: targetIndex", App, StringComparison.Ordinal); Assert.Contains("event.dataTransfer.setData(\"text/x-mbn-tab\"", App, StringComparison.Ordinal); Assert.Contains("marketTabs.addEventListener(\"dragover\"", App, StringComparison.Ordinal); Assert.DoesNotContain("send(\"swap-tabs\"", App, StringComparison.Ordinal); } [Fact] public void FixedCatalogUsesOpaqueCSharpActionAndSectionIntents() { Assert.Contains("state.fixedCatalog", App, StringComparison.Ordinal); Assert.Contains("dataset.actionId", App, StringComparison.Ordinal); Assert.Contains("dataset.sectionIndex", App, StringComparison.Ordinal); Assert.Contains("send(\"activate-fixed-action\"", App, StringComparison.Ordinal); Assert.Contains("send(\"activate-fixed-section\"", App, StringComparison.Ordinal); Assert.Contains("send(\"activate-industry-section\"", App, StringComparison.Ordinal); Assert.Contains("send(\"activate-comparison-section\"", App, StringComparison.Ordinal); Assert.Contains("dataset.industrySectionIndex", App, StringComparison.Ordinal); Assert.Contains("dataset.comparisonSectionIndex", App, StringComparison.Ordinal); Assert.Contains("send(\"set-moving-averages\"", App, StringComparison.Ordinal); } [Fact] public void IndustryWorkspaceUsesIndexesAndOpaqueActionIds() { Assert.Contains("state.industry", App, StringComparison.Ordinal); Assert.Contains("dataset.industryIndex", App, StringComparison.Ordinal); Assert.Contains("dataset.industryActionId", App, StringComparison.Ordinal); Assert.Contains("send(\"select-industry\"", App, StringComparison.Ordinal); Assert.Contains("send(\"double-click-industry\"", App, StringComparison.Ordinal); Assert.Contains("send(\"swap-industries\"", App, StringComparison.Ordinal); Assert.Contains("send(\"activate-industry-action\"", App, StringComparison.Ordinal); } [Fact] public void ComparisonWorkspaceUsesOnlyOpaqueTargetPairAndActionIds() { foreach (var value in new[] { "search-comparison-domestic", "search-comparison-world", "select-comparison-domestic", "select-comparison-world", "select-comparison-fixed", "choose-comparison-domestic", "choose-comparison-world", "choose-comparison-fixed", "swap-comparison-targets", "clear-comparison-targets", "add-comparison-pair", "select-comparison-pair", "move-comparison-pair", "delete-selected-comparison-pair", "delete-all-comparison-pairs", "activate-comparison-action" }) { Assert.Contains($"\"{value}\"", App, StringComparison.Ordinal); } Assert.Contains("dataset.comparisonSelectionId", App, StringComparison.Ordinal); Assert.Contains("dataset.comparisonPairId", App, StringComparison.Ordinal); Assert.Contains("dataset.comparisonActionId", App, StringComparison.Ordinal); Assert.Contains("comparisonTargetClickTimer", App, StringComparison.Ordinal); Assert.DoesNotContain("stockCode", App, StringComparison.Ordinal); } [Fact] public void ThemeWorkspaceUsesOpaqueSelectionSortAndActionIds() { Assert.Contains("state.theme", App, StringComparison.Ordinal); Assert.Contains("dataset.themeResultId", App, StringComparison.Ordinal); Assert.Contains("dataset.themeActionId", App, StringComparison.Ordinal); Assert.Contains("send(\"search-themes\"", App, StringComparison.Ordinal); Assert.Contains("send(\"select-theme\"", App, StringComparison.Ordinal); Assert.Contains("send(\"set-theme-sort\"", App, StringComparison.Ordinal); Assert.Contains("send(\"activate-theme-action\"", App, StringComparison.Ordinal); } [Fact] public void ExpertAndTradingHaltWorkspacesUseOpaqueIntents() { foreach (var intent in new[] { "search-experts", "select-expert", "activate-expert-action", "search-trading-halts", "select-trading-halt", "toggle-trading-halt-sort", "activate-trading-halt-action" }) { Assert.Contains($"send(\"{intent}\"", App, StringComparison.Ordinal); } } [Fact] public void DbSaveLoadUseClosedNamedPlaylistIntentsWhilePlayoutRemainsDisabled() { foreach (var intent in new[] { "refresh-named-playlists", "select-named-playlist", "load-selected-named-playlist", "create-named-playlist", "save-current-named-playlist", "delete-selected-named-playlist" }) { Assert.Contains($"\"{intent}\"", App, StringComparison.Ordinal); } Assert.Contains("state.namedPlaylist", App, StringComparison.Ordinal); Assert.Contains("dataset.namedPlaylistDefinitionId", App, StringComparison.Ordinal); Assert.Contains("definitionId: definition.dataset.namedPlaylistDefinitionId", App, StringComparison.Ordinal); Assert.Contains("dbLoadButton.disabled = busy || !named", App, StringComparison.Ordinal); Assert.Contains("named.isWriteQuarantined", App, StringComparison.Ordinal); Assert.DoesNotContain("programCode", App, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("listText", App, StringComparison.OrdinalIgnoreCase); foreach (var label in new[] { "TAKE IN", "NEXT", "TAKE OUT" }) { Assert.Matches($"]*disabled[^>]*>[^<]*{Regex.Escape(label)}", Markup); } } [Fact] public void SeparatorRowsPreserveOriginalDragGesture() { Assert.Contains("element.draggable = true", App, StringComparison.Ordinal); Assert.DoesNotContain("draggable = !row.isSeparator", App, StringComparison.Ordinal); } [Fact] public void BusySnapshotFreezesControlsWithoutQueueingInput() { Assert.Contains("state.isBusy === true", App, StringComparison.Ordinal); Assert.Contains("classList.toggle(\"busy\"", App, StringComparison.Ordinal); Assert.Contains( "body.busy .operator-shell { pointer-events: none; }", Styles, StringComparison.Ordinal); } private static string FindRepositoryRoot() { DirectoryInfo? directory = new(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("The repository root could not be located."); } }