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.
243 lines
8.9 KiB
C#
243 lines
8.9 KiB
C#
using System.Buffers.Binary;
|
|
|
|
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
|
|
|
|
public sealed class LegacyStartupIntroNativeContractTests
|
|
{
|
|
private static readonly string RepositoryRoot = FindRepositoryRoot();
|
|
private static readonly string AppRoot = Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp");
|
|
private static readonly string WindowXaml = File.ReadAllText(
|
|
Path.Combine(AppRoot, "MainWindow.xaml"));
|
|
private static readonly string Window = File.ReadAllText(
|
|
Path.Combine(AppRoot, "MainWindow.xaml.cs"));
|
|
private static readonly string StartupIntro = File.ReadAllText(
|
|
Path.Combine(AppRoot, "MainWindow.StartupIntro.cs"));
|
|
private static readonly string Manifest = File.ReadAllText(
|
|
Path.Combine(AppRoot, "Package.appxmanifest"));
|
|
private static readonly string Project = File.ReadAllText(
|
|
Path.Combine(AppRoot, "MBN_STOCK_WEBVIEW.LegacyParityApp.csproj"));
|
|
|
|
[Fact]
|
|
public void IntroUsesOnlyPackagedBrandAssetsAndCompactCopy()
|
|
{
|
|
var overlay = Slice(
|
|
WindowXaml,
|
|
"x:Name=\"LoadingOverlay\"",
|
|
"<InfoBar");
|
|
|
|
Assert.Contains(
|
|
"ms-appx:///Assets/Square150x150Logo.scale-200.png",
|
|
overlay,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"ms-appx:///Assets/StartupHero.png",
|
|
overlay,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"ms-appx:///Web/Brand/logo.png",
|
|
overlay,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("V-STOCK", overlay, StringComparison.Ordinal);
|
|
Assert.Contains("증권정보 송출 시스템", overlay, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("http://", overlay, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("https://", overlay, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("원본 호환 화면을 준비하고 있습니다.", overlay,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void GeneratedBroadcastHeroIsAProjectPackagedWidePng()
|
|
{
|
|
const string heroInclude =
|
|
"<Content Include=\"..\\..\\Assets\\StartupHero.png\" Link=\"Assets\\StartupHero.png\" />";
|
|
Assert.Contains(heroInclude, Project, StringComparison.Ordinal);
|
|
|
|
var heroPath = Path.Combine(RepositoryRoot, "Assets", "StartupHero.png");
|
|
var bytes = File.ReadAllBytes(heroPath);
|
|
Assert.True(bytes.Length > 500_000);
|
|
Assert.Equal(
|
|
new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 },
|
|
bytes[..8]);
|
|
Assert.True(bytes.Length >= 24);
|
|
var width = BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(16, 4));
|
|
var height = BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(20, 4));
|
|
Assert.True(width >= 1600);
|
|
Assert.True(height >= 900);
|
|
Assert.True((double)width / height > 1.7);
|
|
}
|
|
|
|
[Fact]
|
|
public void IntroUsesTheDeepNavyMbnAccentAndShortNativeMotion()
|
|
{
|
|
var overlay = Slice(
|
|
WindowXaml,
|
|
"x:Name=\"LoadingOverlay\"",
|
|
"<InfoBar");
|
|
|
|
Assert.Contains("#FF08182B", WindowXaml, StringComparison.OrdinalIgnoreCase);
|
|
Assert.Contains("#FFF47B20", overlay, StringComparison.OrdinalIgnoreCase);
|
|
Assert.Contains(
|
|
"x:Name=\"StartupIntroEnterStoryboard\"",
|
|
WindowXaml,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"x:Name=\"StartupIntroExitStoryboard\"",
|
|
WindowXaml,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("IsIndeterminate=\"True\"", overlay, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"AutomationProperties.Name=\"매일경제TV V-STOCK 시작 화면\"",
|
|
overlay,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"AutomationProperties.AccessibilityView=\"Content\"",
|
|
overlay,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void EveryStartupOutcomeKeepsItsCancellableTwoSecondHold()
|
|
{
|
|
Assert.Contains(
|
|
"TimeSpan.FromMilliseconds(2050)",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"TimeSpan.FromMilliseconds(180)",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("UISettings", StartupIntro, StringComparison.Ordinal);
|
|
Assert.Contains(".AnimationsEnabled", StartupIntro, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Interlocked.CompareExchange(",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"_lifetimeCancellation.Token",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("Task.Delay(", StartupIntro, StringComparison.Ordinal);
|
|
Assert.DoesNotContain(
|
|
"skipMinimumDuration",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"if (!_startupIntroAnimationsEnabled)",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void WebReadyPostsTheFirstStateBeforeEveryOutcomeUsesTheSameDismissal()
|
|
{
|
|
var dispatch = Slice(
|
|
Window,
|
|
"private async Task ProcessIntentAsync(",
|
|
"private static bool IsNamedPlaylistModalIntent(");
|
|
var finalPost = RequiredIndex(dispatch, "PostState(state);");
|
|
var readyDismiss = RequiredIndex(
|
|
dispatch,
|
|
"if (intent is LegacyReadyIntent)",
|
|
finalPost);
|
|
var normalDismiss = RequiredIndex(
|
|
dispatch,
|
|
"BeginDismissStartupIntro();",
|
|
readyDismiss);
|
|
Assert.True(finalPost < readyDismiss);
|
|
Assert.True(readyDismiss < normalDismiss);
|
|
|
|
var failurePost = RequiredIndex(dispatch, "PostState(failureState);");
|
|
var immediateDismiss = RequiredIndex(
|
|
dispatch,
|
|
"BeginDismissStartupIntro();",
|
|
failurePost);
|
|
Assert.True(failurePost < immediateDismiss);
|
|
|
|
var navigation = Slice(
|
|
Window,
|
|
"private void OnNavigationCompleted(",
|
|
"private static void OnNewWindowRequested(");
|
|
Assert.Contains("if (!args.IsSuccess)", navigation, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"BeginDismissStartupIntro();",
|
|
navigation,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain(
|
|
"LoadingOverlay.Visibility = Visibility.Collapsed;",
|
|
navigation,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void IntroBlocksInputUntilItsOneShotDismissalCompletes()
|
|
{
|
|
var browser = Slice(WindowXaml, "<WebView2", "/>");
|
|
Assert.Contains("x:Name=\"Browser\"", browser, StringComparison.Ordinal);
|
|
Assert.Contains("IsHitTestVisible=\"False\"", browser, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"LoadingOverlay.Visibility = Visibility.Collapsed;",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"LoadingOverlay.IsHitTestVisible = false;",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Browser.IsHitTestVisible = true;",
|
|
StartupIntro,
|
|
StringComparison.Ordinal);
|
|
|
|
var loaded = Slice(
|
|
Window,
|
|
"private async void OnRootLoaded(",
|
|
"private void ConfigureWindow()");
|
|
Assert.Contains("StartStartupIntro();", loaded, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void MsixSystemSplashMatchesTheNativeIntroCanvas()
|
|
{
|
|
Assert.Contains(
|
|
"BackgroundColor=\"#08182B\"",
|
|
Manifest,
|
|
StringComparison.OrdinalIgnoreCase);
|
|
Assert.Contains(
|
|
"<uap:SplashScreen Image=\"Assets\\SplashScreen.png\" />",
|
|
Manifest,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
private static string Slice(string value, string start, string end)
|
|
{
|
|
var startIndex = RequiredIndex(value, start);
|
|
var endIndex = RequiredIndex(value, end, startIndex + start.Length);
|
|
return value[startIndex..endIndex];
|
|
}
|
|
|
|
private static int RequiredIndex(string value, string needle, int startIndex = 0)
|
|
{
|
|
var index = value.IndexOf(needle, startIndex, StringComparison.Ordinal);
|
|
Assert.True(index >= 0, $"Required marker was not found: {needle}");
|
|
return index;
|
|
}
|
|
|
|
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.");
|
|
}
|
|
}
|