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.
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user