Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.LegacyWeb.Tests/LegacySidebarIconContractTests.cs
Wickedness b050f2f06c feat: refine branded operator experience
Add the MBN-branded startup intro, semantic sidebar icons, and refined card-based operator visuals. Strengthen packaged UI and input smoke coverage for the updated experience.
2026-07-24 01:26:13 +09:00

239 lines
9.2 KiB
C#

namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
public sealed class LegacySidebarIconContractTests
{
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 Navigation = File.ReadAllText(
Path.Combine(WebRoot, "workspace-navigation.js"));
private static readonly string Styles = File.ReadAllText(
Path.Combine(WebRoot, "styles.css"));
private static readonly string PackageInputSmoke = File.ReadAllText(
Path.Combine(RepositoryRoot, "scripts", "Test-LegacyPackageInput.mjs"));
private static readonly string UiOnlySmoke = File.ReadAllText(
Path.Combine(RepositoryRoot, "scripts", "Test-LegacyPackageUiOnlySmoke.mjs"));
[Fact]
public void EverySidebarDestinationUsesOneClosedSemanticSvgIcon()
{
var mappings = new (string Anchor, string Icon)[]
{
("id=\"workspace-stock-tab\"", "stock-cut"),
("data-tab-id=\"overseas\"", "overseas"),
("data-tab-id=\"exchange\"", "exchange"),
("data-tab-id=\"index\"", "index"),
("data-tab-id=\"kospiIndustry\"", "kospi"),
("data-tab-id=\"kosdaqIndustry\"", "kosdaq"),
("data-tab-id=\"comparison\"", "comparison"),
("data-tab-id=\"theme\"", "theme"),
("data-tab-id=\"overseasStocks\"", "overseas-stocks"),
("data-tab-id=\"expert\"", "expert"),
("data-tab-id=\"tradingHalt\"", "trading-halt"),
("id=\"workspace-settings-tab\"", "settings")
};
foreach (var (anchor, icon) in mappings)
{
var button = Slice(Markup, anchor, "</button>");
Assert.Contains($"data-icon=\"{icon}\"", button, StringComparison.Ordinal);
Assert.Contains(
$"href=\"#workspace-icon-{icon}\"",
button,
StringComparison.Ordinal);
Assert.DoesNotContain("<img", button, StringComparison.OrdinalIgnoreCase);
}
var sprite = Slice(
Markup,
"<svg class=\"workspace-icon-sprite\"",
"</svg>");
Assert.DoesNotContain("<text", sprite, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("http://", sprite, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("https://", sprite, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void KospiAndKosdaqUseDistinctFontIndependentMonograms()
{
var kospi = Slice(
Markup,
"<symbol id=\"workspace-icon-kospi\"",
"</symbol>");
var kosdaq = Slice(
Markup,
"<symbol id=\"workspace-icon-kosdaq\"",
"</symbol>");
Assert.Contains(
"M4.5 18V6h4a4 4 0 0 1 0 8h-4",
kospi,
StringComparison.Ordinal);
Assert.Contains(
"M15 6h4M17 6v12M15 18h4",
kospi,
StringComparison.Ordinal);
Assert.Contains(
"M2.3 18V6h1.6c2.8 0 4.3 2.2 4.3 6",
kosdaq,
StringComparison.Ordinal);
Assert.Contains(
"M9 18l2.5-12H13l2.5 12M10 14h4.5",
kosdaq,
StringComparison.Ordinal);
Assert.Contains(
"<ellipse cx=\"19.2\" cy=\"12\" rx=\"3.1\" ry=\"6\" stroke-width=\"1.65\">",
kosdaq,
StringComparison.Ordinal);
Assert.Contains("m19.5 14.5 3.2 4", kosdaq, StringComparison.Ordinal);
Assert.DoesNotContain("<text", kospi + kosdaq, StringComparison.OrdinalIgnoreCase);
Assert.NotEqual(kospi, kosdaq);
}
[Fact]
public void HeadingClonesTheSelectedIconAndThemeStylingStaysVectorNative()
{
Assert.Contains(
"const titleIcon = document.querySelector(\".workspace-heading-icon\")",
Navigation,
StringComparison.Ordinal);
var sync = Slice(
Navigation,
"function syncHeadingIcon(button)",
"function refreshCurrentMenu()");
Assert.Contains("source.cloneNode(true)", sync, StringComparison.Ordinal);
Assert.Contains("titleIcon.replaceChildren(next)", sync, StringComparison.Ordinal);
var workspace = Slice(
Navigation,
"function setWorkspace(name)",
"function applyStartPreference(mode)");
AssertInOrder(
workspace,
"activeWorkspace = name;",
"refreshCurrentMenu();",
"syncHeadingIcon(selectedButton);",
"title.textContent");
Assert.Contains(
".workspace-icon-sprite",
Styles,
StringComparison.Ordinal);
Assert.Contains(
".workspace-nav-icon { shape-rendering: geometricPrecision; }",
Styles,
StringComparison.Ordinal);
Assert.Contains(
".workspace-nav-toggle svg, .workspace-nav-item svg",
Styles,
StringComparison.Ordinal);
Assert.Contains("stroke: currentColor;", Styles, StringComparison.Ordinal);
}
[Fact]
public void PackageSmokesProveIconsArePaintedMappedAndCentered()
{
var mappings = new (string TabId, string Icon)[]
{
("overseas", "overseas"),
("exchange", "exchange"),
("index", "index"),
("kospiIndustry", "kospi"),
("kosdaqIndustry", "kosdaq"),
("comparison", "comparison"),
("theme", "theme"),
("overseasStocks", "overseas-stocks"),
("expert", "expert"),
("tradingHalt", "trading-halt")
};
foreach (var harness in new[] { PackageInputSmoke, UiOnlySmoke })
{
foreach (var (tabId, icon) in mappings)
{
Assert.Contains(
$"{tabId}: \"{icon}\"",
harness,
StringComparison.Ordinal);
}
Assert.Contains("svg?.getBBox?.()", harness, StringComparison.Ordinal);
Assert.Contains("reference:", harness, StringComparison.Ordinal);
Assert.Contains("Number.parseFloat(style.opacity) > 0",
harness,
StringComparison.Ordinal);
Assert.Contains("style.stroke !== \"none\" || style.fill !== \"none\"",
harness,
StringComparison.Ordinal);
Assert.Contains("rendered: Boolean(", harness, StringComparison.Ordinal);
Assert.Contains("expectedHeadingIcon", harness, StringComparison.Ordinal);
}
Assert.Contains("navigationItems:", PackageInputSmoke, StringComparison.Ordinal);
Assert.Contains("iconCenterOffset:", PackageInputSmoke, StringComparison.Ordinal);
Assert.Contains("iconCenterOffsetY:", PackageInputSmoke, StringComparison.Ordinal);
Assert.Contains("item.iconCenterOffset > 1.5", PackageInputSmoke, StringComparison.Ordinal);
Assert.Contains("item.iconCenterOffsetY > 1.5",
PackageInputSmoke,
StringComparison.Ordinal);
Assert.Contains("collapsedIconCenterOffsets:", PackageInputSmoke, StringComparison.Ordinal);
Assert.Contains("tab.icon.key === baseline.marketTabs[index].icon.key",
PackageInputSmoke,
StringComparison.Ordinal);
Assert.Contains("layout.stockTabIcon.rendered === true",
UiOnlySmoke,
StringComparison.Ordinal);
Assert.Contains("layout.settingsTabIcon.rendered === true",
UiOnlySmoke,
StringComparison.Ordinal);
Assert.Contains("tab.icon.rendered === true",
UiOnlySmoke,
StringComparison.Ordinal);
}
private static string Slice(string value, string start, string end)
{
var startIndex = value.IndexOf(start, StringComparison.Ordinal);
Assert.True(startIndex >= 0, $"Required marker was not found: {start}");
var endIndex = value.IndexOf(
end,
startIndex + start.Length,
StringComparison.Ordinal);
Assert.True(endIndex > startIndex, $"Required marker was not found: {end}");
return value[startIndex..(endIndex + end.Length)];
}
private static void AssertInOrder(string value, params string[] needles)
{
var previous = -1;
foreach (var needle in needles)
{
var current = value.IndexOf(needle, previous + 1, StringComparison.Ordinal);
Assert.True(current > previous, $"Required ordered marker was not found: {needle}");
previous = current;
}
}
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.");
}
}