using System.Text.RegularExpressions; namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests; public sealed class LegacyIndustryListWebContractTests { private static readonly string RepositoryRoot = FindRepositoryRoot(); private static readonly string App = File.ReadAllText(Path.Combine( RepositoryRoot, "src", "MBN_STOCK_WEBVIEW.LegacyParityApp", "Web", "app.js")); [Fact] public void Industry_list_is_one_tab_stop_and_rows_are_programmatic_descendants() { var renderIndustry = Extract( App, " function renderIndustry(state) {", " function renderOverseas(state) {"); var listFactory = Extract( renderIndustry, " const list = document.createElement(\"div\");", " const comparisonTitle = document.createElement(\"strong\");"); Assert.Contains("list.setAttribute(\"role\", \"listbox\");", listFactory, StringComparison.Ordinal); Assert.Contains("list.tabIndex = 0;", listFactory, StringComparison.Ordinal); Assert.Contains("industry.industries.forEach(function (row) {", listFactory, StringComparison.Ordinal); Assert.Contains("button.dataset.industryIndex = String(row.index);", listFactory, StringComparison.Ordinal); Assert.Contains("button.tabIndex = -1;", listFactory, StringComparison.Ordinal); Assert.Contains("button.setAttribute(\"role\", \"option\");", listFactory, StringComparison.Ordinal); Assert.Single(Regex.Matches(listFactory, @"\.tabIndex\s*=\s*0;").Cast()); Assert.DoesNotContain("button.tabIndex = 0;", listFactory, StringComparison.Ordinal); } [Fact] public void Industry_list_handles_arrow_home_and_end_navigation() { var keyHandler = Extract( App, " categoryTree.addEventListener(\"keydown\", function (event) {", " window.addEventListener(\"pointerup\", function () {"); Assert.Contains("const industryList = event.target.closest(\".industry-list\");", keyHandler, StringComparison.Ordinal); Assert.Contains( "[\"ArrowUp\", \"ArrowDown\", \"Home\", \"End\"].includes(event.key)", keyHandler, StringComparison.Ordinal); Assert.Contains("event.preventDefault();", keyHandler, StringComparison.Ordinal); Assert.Contains("if (event.key === \"Home\") nextIndex = 0;", keyHandler, StringComparison.Ordinal); Assert.Contains("else if (event.key === \"End\") nextIndex = rows.length - 1;", keyHandler, StringComparison.Ordinal); Assert.Contains( "else if (event.key === \"ArrowUp\") nextIndex = Math.max(0, selectedIndex - 1);", keyHandler, StringComparison.Ordinal); Assert.Contains( "else nextIndex = Math.min(rows.length - 1, selectedIndex + 1);", keyHandler, StringComparison.Ordinal); Assert.Contains("selectIndustryRow(nextRow);", keyHandler, StringComparison.Ordinal); Assert.Contains("nextRow.scrollIntoView({ block: \"nearest\", inline: \"nearest\" });", keyHandler, StringComparison.Ordinal); } [Fact] public void Selection_only_state_receipt_updates_rows_without_replacing_the_workspace() { var renderIndustry = Extract( App, " function renderIndustry(state) {", " function renderOverseas(state) {"); var structureIdentity = Extract( renderIndustry, " const structureKey = JSON.stringify({", " const existingWorkspace = categoryTree.querySelector(\".industry-workspace\");"); var selectionOnlyReceipt = Extract( renderIndustry, " if (existingWorkspace && structureKey === industryRenderStructureKey) {", " const previousList = categoryTree.querySelector(\".industry-list\");"); Assert.Contains("market: industry.market", structureIdentity, StringComparison.Ordinal); Assert.Contains("rows: industry.industries.map", structureIdentity, StringComparison.Ordinal); Assert.Contains("actions: industry.actions.map", structureIdentity, StringComparison.Ordinal); Assert.DoesNotContain("selectedIndex", structureIdentity, StringComparison.Ordinal); Assert.Contains("existingWorkspace.querySelectorAll(\"button[data-industry-index]\")", selectionOnlyReceipt, StringComparison.Ordinal); Assert.Contains( "const selected = Number(row.dataset.industryIndex) === industry.selectedIndex;", selectionOnlyReceipt, StringComparison.Ordinal); Assert.Contains("row.classList.toggle(\"selected\", selected);", selectionOnlyReceipt, StringComparison.Ordinal); Assert.Contains( "row.setAttribute(\"aria-selected\", selected ? \"true\" : \"false\");", selectionOnlyReceipt, StringComparison.Ordinal); Assert.Contains("return;", selectionOnlyReceipt, StringComparison.Ordinal); Assert.DoesNotContain("replaceChildren", selectionOnlyReceipt, StringComparison.Ordinal); Assert.DoesNotContain("document.createElement", selectionOnlyReceipt, StringComparison.Ordinal); } [Fact] public void Industry_list_is_appended_before_comparison_label_and_slots() { var renderIndustry = Extract( App, " function renderIndustry(state) {", " function renderOverseas(state) {"); Assert.Contains("comparisonTitle.textContent = \"업종비교\";", renderIndustry, StringComparison.Ordinal); Assert.Contains("selector.append(list, comparisonTitle, slots);", renderIndustry, StringComparison.Ordinal); Assert.DoesNotContain("selector.append(comparisonTitle, slots, list);", renderIndustry, StringComparison.Ordinal); } [Fact] public void Comparison_text_boxes_commit_closed_text_intents_without_replacing_focused_dom() { var renderIndustry = Extract( App, " function renderIndustry(state) {", " function renderOverseas(state) {"); var changeHandler = Extract( App, " categoryTree.addEventListener(\"change\", function (event) {", " categoryTree.addEventListener(\"keydown\", function (event) {"); Assert.Contains("first.dataset.industrySlot = \"first\";", renderIndustry, StringComparison.Ordinal); Assert.Contains("second.dataset.industrySlot = \"second\";", renderIndustry, StringComparison.Ordinal); Assert.Contains("first.maxLength = 128;", renderIndustry, StringComparison.Ordinal); Assert.Contains("second.maxLength = 128;", renderIndustry, StringComparison.Ordinal); Assert.DoesNotContain("first.readOnly = true;", renderIndustry, StringComparison.Ordinal); Assert.DoesNotContain("second.readOnly = true;", renderIndustry, StringComparison.Ordinal); Assert.DoesNotContain("firstName: industry.firstName", renderIndustry, StringComparison.Ordinal); Assert.DoesNotContain("secondName: industry.secondName", renderIndustry, StringComparison.Ordinal); Assert.Contains("document.activeElement !== firstInput", renderIndustry, StringComparison.Ordinal); Assert.Contains("document.activeElement !== secondInput", renderIndustry, StringComparison.Ordinal); Assert.Contains("send(\"set-industry-comparison-text\"", changeHandler, StringComparison.Ordinal); Assert.Contains("slot: event.target.dataset.industrySlot", changeHandler, StringComparison.Ordinal); Assert.Contains("value: event.target.value", changeHandler, StringComparison.Ordinal); } private static string Extract(string source, string startToken, string endToken) { var start = source.IndexOf(startToken, StringComparison.Ordinal); var end = source.IndexOf(endToken, start + startToken.Length, StringComparison.Ordinal); Assert.True(start >= 0, $"Start token was not found: {startToken}"); Assert.True(end > start, $"End token was not found after start token: {endToken}"); return source[start..end]; } 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 was not found."); } }