fix: complete legacy operator parity workflows

This commit is contained in:
2026-07-23 00:54:31 +09:00
parent 939c252d23
commit cf117de144
25 changed files with 2430 additions and 223 deletions

View File

@@ -188,8 +188,15 @@ test("named playlist save requests the native saved dialog then closes on exact
assertOrdered(confirmationOpen, [
"rememberModalOpener(namedPlaylistConfirmationDialog)",
"namedPlaylistConfirmationModal.hidden = false",
"namedPlaylistConfirmationYes.disabled = false",
"namedPlaylistConfirmationNo.disabled = false",
"syncModalFocus()"
]);
const confirmationLock = section(
"namedPlaylistConfirmationYes.disabled = pendingNamedPlaylistCommand",
"if (deferredSelectionAction)");
assert.doesNotMatch(confirmationLock, /operatorMutationLocked|uiBusy/,
"an unrelated PROGRAM refresh must not cancel a physical confirmation click");
assertOrdered(resolution, [
"namedPlaylistConfirmationModal.hidden = true",
"syncModalFocus()",

View File

@@ -83,6 +83,31 @@ public sealed class LegacyStockCutInteractionParityTests
row => Assert.Equal(LegacyStockCutCatalog.Rows[1].RawLabel, row.RawCutLabel));
}
[Fact]
public async Task DropPreservesExistingPrefixAndAppendsSelectedCutsAtTheTail()
{
var controller = new LegacyOperatorController(new QueryLookup());
await controller.SearchStocksAsync("first", LegacySearchTrigger.Button);
controller.SelectStock(0);
controller.CutPointerDown(0, false, false, 2, 1, 1_000);
var initial = controller.CutPointerDown(0, false, false, 2, 1, 1_100);
var existing = Assert.Single(initial.Playlist);
controller.CutPointerDown(1, false, false, 2, 24, 2_000);
controller.CutPointerDown(2, true, false, 2, 47, 2_100);
var dropped = controller.DropSelectedCutsOnPlaylist();
Assert.Equal(3, dropped.Playlist.Count);
Assert.Equal(existing, dropped.Playlist[0]);
Assert.Equal(
[
LegacyStockCutCatalog.Rows[1].RawLabel,
LegacyStockCutCatalog.Rows[2].RawLabel
],
dropped.Playlist.Skip(1).Select(row => row.RawCutLabel));
}
[Fact]
public async Task EmptySearchButtonPreservesResultsStockAndCutSelection()
{

View File

@@ -99,6 +99,7 @@ public sealed class LegacyNativeDialogParityWebTests
foreach (var label in new[]
{
"PList confirm row save",
"PROGRAM confirm named-playlist row save",
"GraphE ${screen} save",
"GraphE ${screen} delete",
"GraphE ${screen} delete-all cancel branch",
@@ -110,7 +111,7 @@ public sealed class LegacyNativeDialogParityWebTests
}
Assert.Equal(
7,
8,
PackageInputHarness.Split(
"pointerClickWithNativeDialog(",
StringSplitOptions.None).Length - 1);

View File

@@ -24,6 +24,55 @@ public sealed class LegacyPackageInputSmokeContractTests
StringComparison.Ordinal);
}
[Fact]
public void NamedPlaylistDoubleClickSealUsesAsciiSafeUtf8Constants()
{
Assert.Contains("$ExpectedSavedFixtureGraphicType = $Utf8.GetString(", Runner,
StringComparison.Ordinal);
Assert.Contains("'MeyXtO2MkOq4sOuzuA=='", Runner,
StringComparison.Ordinal);
Assert.Contains("'7ZiE7J6s6rCA'", Runner, StringComparison.Ordinal);
Assert.Contains("'7Iuc6rCE7Jm464uo7J286rCA'", Runner,
StringComparison.Ordinal);
var doubleClick = Slice(
Runner,
"function Invoke-ExactNamedPlaylistDoubleClickRequest",
"function Get-BackgroundPickerWindowHandles");
Assert.Contains("$ExpectedSavedFixtureGraphicType", doubleClick,
StringComparison.Ordinal);
Assert.Contains("$ExpectedSavedFixtureCurrentSubtype", doubleClick,
StringComparison.Ordinal);
Assert.Contains("$ExpectedSavedFixtureAfterHoursSubtype", doubleClick,
StringComparison.Ordinal);
Assert.DoesNotContain("'1열판기본'", doubleClick,
StringComparison.Ordinal);
}
[Fact]
public void PhysicalPointerInputsCaptureGeometryAfterForegroundAcquisition()
{
foreach (var (start, end) in new[]
{
("public static void SendRowHeaderDragAfterThenHome(",
"public static void SendShiftClickRangeThenDeleteAndRestore("),
("public static void SendShiftClickRangeThenDeleteAndRestore(",
"public static void SendApplicationClick("),
("public static void SendApplicationClick(",
"public static void SendApplicationDoubleClick("),
("public static void SendApplicationDoubleClick(",
"public static IntPtr ReadForegroundWindow()")
})
{
var input = Slice(Runner, start, end);
AssertInOrder(
input,
"AcquireForeground(window, processId, forbiddenPort);",
"ClientGeometry baseline = ReadAndAssertClientGeometry(",
"SleepWithWindowGuard(");
}
}
[Fact]
public void DryRunFinalSealKeepsWindowsDragOrderAndRequiresTheSafeTailAppend()
{
@@ -56,13 +105,35 @@ public sealed class LegacyPackageInputSmokeContractTests
StringComparison.Ordinal);
}
[Fact]
public void ReadOnlyPhysicalPlaylistButtonsAndCheckboxUseTheClosedLocalAllowlist()
{
var harnessAllowlist = Slice(
Harness,
"const readOnlyAllowedOutboundMessageTypes",
"const fullUiDbAllowedOutboundMessageTypes");
Assert.Contains("\"move-selected-playlist-rows\"", harnessAllowlist,
StringComparison.Ordinal);
Assert.Contains("\"set-playlist-enabled\"", harnessAllowlist,
StringComparison.Ordinal);
var runnerAllowlist = Slice(
Runner,
"$allowedOutbound = @(",
"if ($HarnessProfile -ceq 'DryRunPlayout')");
Assert.Contains("'move-selected-playlist-rows'", runnerAllowlist,
StringComparison.Ordinal);
Assert.Contains("'set-playlist-enabled'", runnerAllowlist,
StringComparison.Ordinal);
}
[Fact]
public void DryRunSealPinsProgramEnableAndShortcutEvidenceExactly()
{
var seal = Slice(
Runner,
"$playoutIntentContractOk =",
"if ([int]$evidence.schemaVersion");
"$screenshotPathOk =");
foreach (var token in new[]
{
"programEnableParity.currentEntryId",
@@ -71,6 +142,9 @@ public sealed class LegacyPackageInputSmokeContractTests
"programEnableParity.setAllIntentCount -eq 2",
"programEnableParity.toggleActiveIntentCount -eq 1",
"programEnableParity.endOfPlaylistObserved -eq $true",
"programDragParity.appendIntentCount -eq 1",
"programDragParity.cleanupDeleteIntentCount -eq 1",
"programDragParity.onAirEntryPreserved -eq $true",
"shortcutParity.f8IntentCount -eq 1",
"shortcutParity.escapeIntentCount -eq 1",
"shortcutParity.finalPhase -ceq 'idle'",
@@ -91,6 +165,78 @@ public sealed class LegacyPackageInputSmokeContractTests
StringComparison.Ordinal);
}
[Fact]
public void DryRunPhysicallyCancelsF2PickerAndFailClosesMissingF3Default()
{
var nativeInput = Slice(
Runner,
"public static void SendBackgroundFunctionKey(",
"public static void SendEscapeToForegroundWindow");
Assert.Contains("key != VirtualKeyF2 && key != VirtualKeyF3", nativeInput,
StringComparison.Ordinal);
Assert.Contains("AssertNoForbiddenTcp(processId, forbiddenPort)", nativeInput,
StringComparison.Ordinal);
Assert.Contains("allowForegroundTransitionAfterKey", nativeInput,
StringComparison.Ordinal);
var fixture = Slice(
Runner,
"function Initialize-BackgroundShortcutFixture",
"function Assert-NoUnsafeEnvironment");
Assert.Contains("$Profile -cne 'DryRunPlayout'", fixture,
StringComparison.Ordinal);
Assert.Contains("$ExpectedInstallLocation", fixture,
StringComparison.Ordinal);
Assert.Contains("[IO.FileAttributes]::ReparsePoint", fixture,
StringComparison.Ordinal);
Assert.Contains("The F3 fail-closed test requires the external default background asset to be absent.",
fixture, StringComparison.Ordinal);
Assert.Contains("@(Get-ChildItem -LiteralPath $root -Force).Count -ne 0", fixture,
StringComparison.Ordinal);
Assert.Contains("[IO.Directory]::Delete($root, $false)", fixture,
StringComparison.Ordinal);
var picker = Slice(
Runner,
"function Get-BackgroundPickerWindowHandles",
"function Invoke-ExactWindowsInputRequest");
foreach (var token in new[]
{
"WindowClassName($handle) -cne '#32770'",
"$BackgroundPickerTitle",
"WindowProcessId($handle) -ne $Application.Id",
"OwnerChainContains(",
"The exact app-owned background picker is not modal.",
"SendEscapeToForegroundWindow($picker.Handle)",
"The exact background picker was not destroyed after the one Escape input.",
"The external default background appeared; the one F3 fail-closed input was not sent."
})
{
Assert.Contains(token, picker, StringComparison.Ordinal);
}
var harness = Slice(
Harness,
"async function requestPhysicalBackgroundShortcuts()",
"async function installProbe()");
AssertInOrder(
harness,
"operation: \"background-f2-picker-cancel\"",
"[\"choose-background\"]",
"operation: \"background-f3-none\"",
"snapshot.state.statusKind === \"error\"",
"[\"toggle-background\"]",
"defaultAssetAbsentFailClosed: true");
Assert.Contains("return 3", Runner, StringComparison.Ordinal);
Assert.Contains("backgroundShortcutParity.f2KeyCalls -eq 1", Runner,
StringComparison.Ordinal);
Assert.Contains("backgroundShortcutParity.f3KeyCalls -eq 1", Runner,
StringComparison.Ordinal);
Assert.Contains("backgroundShortcutParity.finalBackgroundEnabled -eq $false", Runner,
StringComparison.Ordinal);
}
[Fact]
public void NamedPlaylistRecoveryIsStrictlyScopedForwardedAndSealed()
{
@@ -126,7 +272,9 @@ public sealed class LegacyPackageInputSmokeContractTests
"$recoveryEvidence.writeCount -eq 1",
"@('committedFresh', 'committedOptimistic')",
"$namedPlaylistDeleteWriteCount -eq 2");
Assert.Contains("-not $namedPlaylistRecoveryContractOk", Runner,
Assert.Contains(
"namedPlaylistRecovery = [bool]$namedPlaylistRecoveryContractOk",
Runner,
StringComparison.Ordinal);
}
@@ -354,6 +502,11 @@ public sealed class LegacyPackageInputSmokeContractTests
sequence,
"const f8Authorization = authorizeDryRunPlayoutShortcut(",
"\"rapid DryRun F8 after row selection\"",
"PROGRAM captured-pointer safe-tail drag",
"PROGRAM captured-pointer tail append",
"delete PROGRAM drag-appended future tail",
"PROGRAM drag-appended tail cleanup",
"PROGRAM safe-tail double-click append",
"PROGRAM clear every enabled row",
"snapshot.state.playlist.every(row => row.isEnabled === false)",
"snapshot.state.playout.nextKind === \"endOfPlaylist\"",
@@ -377,6 +530,143 @@ public sealed class LegacyPackageInputSmokeContractTests
StringComparison.Ordinal);
Assert.Contains("toggleActiveIntentCount: 1", sequence,
StringComparison.Ordinal);
Assert.Contains("programDragParity =", sequence,
StringComparison.Ordinal);
Assert.Contains("onAirEntryPreserved: true", sequence,
StringComparison.Ordinal);
}
[Fact]
public void ProgramNamedPlaylistProfilePhysicallyCreatesSavesReadsAndDeletesWhileOnAir()
{
Assert.Contains("'ProgramPlaylistDb'", Runner, StringComparison.Ordinal);
Assert.Contains("'program-playlist-db'", Runner, StringComparison.Ordinal);
Assert.Contains("\"program-playlist-db\"", Harness, StringComparison.Ordinal);
var profile = Slice(
Harness,
"const programPlaylistDbProfile",
"const hardDeadlineEpochMilliseconds");
AssertInOrder(
profile,
"playout-background-file",
"playout-background-none",
"playout-prepare",
"playout-next",
"db-load-button");
foreach (var allowed in new[]
{
"create-named-playlist",
"save-current-named-playlist-to",
"delete-selected-named-playlist",
"dismiss-dialog",
"take-in",
"take-out"
})
{
Assert.Contains($"\"{allowed}\"", profile, StringComparison.Ordinal);
}
var sequence = Slice(
Harness,
"async function executeProgramNamedPlaylistCrud",
"const manualFinancialScreens");
AssertInOrder(
sequence,
"enter DryRun PROGRAM for named-playlist persistence",
"PROGRAM open named-playlist save dialog",
"PROGRAM create named-playlist definition",
"PROGRAM confirm named-playlist row save",
"PROGRAM reopen save dialog for fresh readback",
"PROGRAM named-playlist fresh readback",
"PROGRAM confirm named-playlist delete",
"PROGRAM named-playlist delete and absence",
"leave DryRun PROGRAM after named-playlist CRUD",
"PROGRAM named-playlist final IDLE");
Assert.DoesNotContain(
"document.getElementById(\"db-load-button\")",
sequence,
StringComparison.Ordinal);
var safeSequence = Slice(
Harness,
"async function executeSafeSequence",
"async function executeDryRunPlayoutSequence");
Assert.Contains("if (!programPlaylistDbProfile)", safeSequence,
StringComparison.Ordinal);
Assert.Contains("document.getElementById(\"db-load-button\")", safeSequence,
StringComparison.Ordinal);
Assert.Contains("writes.length !== 3", sequence, StringComparison.Ordinal);
Assert.Contains("\"저장되었습니다\"", sequence, StringComparison.Ordinal);
Assert.DoesNotContain("?€?λ릺?덉뒿?덈떎", sequence,
StringComparison.Ordinal);
Assert.Contains("definitionId: readback.definitionId", sequence,
StringComparison.Ordinal);
Assert.Contains("saveDefinitionId: created.definitionId", sequence,
StringComparison.Ordinal);
Assert.Contains(
"snapshot.state.namedPlaylist.documentFreshness === \"fresh\"",
sequence,
StringComparison.Ordinal);
Assert.Contains(
"snapshot.state.namedPlaylist.listFreshness === \"fresh\"",
sequence,
StringComparison.Ordinal);
Assert.Contains(
"snapshot.state.namedPlaylist.documentFreshness === \"none\"",
sequence,
StringComparison.Ordinal);
Assert.Contains("snapshot.state.namedPlaylist.rowCount === 0", sequence,
StringComparison.Ordinal);
Assert.Contains("rowCount: savedRowCount", sequence,
StringComparison.Ordinal);
Assert.Contains("freshDefinitionReadbackVerified: true", sequence,
StringComparison.Ordinal);
Assert.Contains("exactAbsenceVerified: true", sequence,
StringComparison.Ordinal);
Assert.Contains("beforeProgramDeleteWriteCount", sequence,
StringComparison.Ordinal);
Assert.Contains("waitForNamedPlaylistDeleteDispatch(", sequence,
StringComparison.Ordinal);
var finalIdle = Slice(
sequence,
"PROGRAM named-playlist final IDLE",
"restore deletion selection after PROGRAM named-playlist CRUD");
Assert.Contains("allowActiveDryRun: true", finalIdle,
StringComparison.Ordinal);
var deleteDispatch = Slice(
Harness,
"async function waitForNamedPlaylistDeleteDispatch",
"function assertNamedPlaylistRecoverySafe");
Assert.Contains("Date.now() + 5_000", deleteDispatch,
StringComparison.Ordinal);
Assert.Contains("writeCount === 1", deleteDispatch,
StringComparison.Ordinal);
Assert.Contains("produced no delete intent", deleteDispatch,
StringComparison.Ordinal);
Assert.Contains("failKnown", deleteDispatch, StringComparison.Ordinal);
Assert.Contains("failUnknown", deleteDispatch, StringComparison.Ordinal);
var seal = Slice(
Runner,
"elseif ($HarnessProfile -ceq 'ProgramPlaylistDb')",
"$playoutIntentContractOk =");
foreach (var token in new[]
{
"approvedDatabaseWriteMessages).Count -eq 3",
"create-named-playlist",
"save-current-named-playlist-to",
"delete-selected-named-playlist",
"saveDefinitionId",
"definitionId",
"requestId",
"freshDefinitionReadbackVerified -eq $true",
"exactAbsenceVerified -eq $true",
"finalPhase -ceq 'idle'"
})
{
Assert.Contains(token, seal, StringComparison.Ordinal);
}
}
[Fact]
@@ -460,6 +750,46 @@ public sealed class LegacyPackageInputSmokeContractTests
"SendMouseMove(restore.X, restore.Y)");
}
[Fact]
public void ReadOnlyPhysicalSequenceDirectlyClicksPlaylistButtonsAndIndividualCheckbox()
{
var sequence = Slice(
Harness,
"async function executeSafeSequence",
"async function executeDryRunPlayoutSequence");
AssertInOrder(
sequence,
"move selected playlist row with Up button",
"playlist Up button reorder",
"restore selected playlist row with Down button",
"playlist Down button reorder",
"disable playlist row with its checkbox",
"individual playlist checkbox disabled row",
"restore playlist row with its checkbox",
"individual playlist checkbox restored row",
"stage direct selected-delete button row",
"select direct button delete fixture",
"delete selected playlist row with button",
"selected-delete button removed exact fixture");
Assert.Contains(
"document.getElementById(\"playlist-move-up\")",
sequence,
StringComparison.Ordinal);
Assert.Contains(
"document.getElementById(\"playlist-move-down\")",
sequence,
StringComparison.Ordinal);
Assert.Contains(
"document.getElementById(\"playlist-delete-selected\")",
sequence,
StringComparison.Ordinal);
Assert.Contains(
"input[type=\"checkbox\"]",
sequence,
StringComparison.Ordinal);
}
[Fact]
public void FullUiDbPListUsesASecondPhysicalDoubleClickExchangeWithContentRestoration()
{

View File

@@ -118,7 +118,7 @@ public sealed class LegacyPlayoutNativeContractTests
}
[Fact]
public void LocalSelectionAndDoubleClickGesturesQueueBehindRefreshInArrivalOrder()
public void LocalSelectionDoubleClickAndCutDropQueueBehindRefreshInArrivalOrder()
{
var process = Normalize(Slice(
Window,
@@ -144,13 +144,13 @@ public sealed class LegacyPlayoutNativeContractTests
Assert.Contains("LegacyActivateOperatorCatalogStockIntent", classifier,
StringComparison.Ordinal);
Assert.Contains("LegacyCutPointerDownIntent", classifier, StringComparison.Ordinal);
Assert.Contains("LegacyDropSelectedCutsIntent", classifier, StringComparison.Ordinal);
Assert.Contains("LegacyCutKeyDownIntent", classifier, StringComparison.Ordinal);
Assert.Contains("LegacyClearCutSelectionIntent", classifier, StringComparison.Ordinal);
Assert.Contains("LegacySelectPlaylistRowIntent", classifier, StringComparison.Ordinal);
Assert.Contains("LegacyActivatePlaylistRowIntent", classifier, StringComparison.Ordinal);
Assert.Contains("LegacySelectPlaylistBoundaryIntent", classifier, StringComparison.Ordinal);
Assert.DoesNotContain("LegacySearchStocksIntent", classifier, StringComparison.Ordinal);
Assert.DoesNotContain("LegacyDropSelectedCutsIntent", classifier, StringComparison.Ordinal);
Assert.DoesNotContain("LegacyExecutePlayoutIntent", classifier, StringComparison.Ordinal);
Assert.DoesNotContain("LegacySave", classifier, StringComparison.Ordinal);
}
@@ -474,8 +474,20 @@ public sealed class LegacyPlayoutNativeContractTests
Assert.Contains("CanDeleteSelectedPlaylistTail(status, workflow)", Playout,
StringComparison.Ordinal);
Assert.Contains("isSafeCutTailAppend", Playout, StringComparison.Ordinal);
Assert.Contains("CanAppendCutToPlaylistTail(status, workflow)", Playout,
StringComparison.Ordinal);
var safeCutTailClassifier = Slice(
Playout,
"var isSafeCutTailAppend",
"// MainForm kept its Space");
Assert.Contains("LegacyCutPointerDownIntent { AllowAppend: true }",
safeCutTailClassifier, StringComparison.Ordinal);
Assert.Contains("LegacyDropSelectedCutsIntent", safeCutTailClassifier,
StringComparison.Ordinal);
Assert.Contains("isSafeProgramEnabledStateMutation", Playout,
StringComparison.Ordinal);
Assert.Contains("isSafeProgramTailDelete", Playout,
StringComparison.Ordinal);
Assert.Contains("refreshAllowsMutation", Playout, StringComparison.Ordinal);
Assert.Contains("!status.IsPlayCompletionPending", Playout, StringComparison.Ordinal);
Assert.Contains("!status.IsTakeOutCompletionPending", Playout, StringComparison.Ordinal);
@@ -539,6 +551,8 @@ public sealed class LegacyPlayoutNativeContractTests
"private static bool IsFixedSectionBatchMutationIntent");
Assert.Contains("LegacyCutPointerDownIntent { AllowAppend: true }", mutationClassifier,
StringComparison.Ordinal);
Assert.Contains("LegacyDropSelectedCutsIntent", mutationClassifier,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacyCutKeyDownIntent", mutationClassifier,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacyClearCutSelectionIntent", mutationClassifier,
@@ -554,6 +568,47 @@ public sealed class LegacyPlayoutNativeContractTests
"private static bool IsOrderedPlayoutIntent");
Assert.Contains("allowAppend: false", selectionOnly,
StringComparison.Ordinal);
var queuedLocalIntents = Slice(
Window,
"private static bool IsQueuedLocalSelectionIntent",
"private static bool IsOperatorSettingsIntent");
Assert.Contains("LegacyDropSelectedCutsIntent", queuedLocalIntents,
StringComparison.Ordinal);
var safeTailAdmission = Slice(
Playout,
"private bool CanAppendCutToPlaylistTail(",
"private static bool RequiresIdlePlaylistMutation");
Assert.Contains("status.OnAirSceneName", safeTailAdmission,
StringComparison.Ordinal);
Assert.Contains("workflow.OnAirCutCode", safeTailAdmission,
StringComparison.Ordinal);
Assert.Contains("workflow.CurrentEntryId", safeTailAdmission,
StringComparison.Ordinal);
Assert.Contains("workflow.CurrentCueIndexZeroBased", safeTailAdmission,
StringComparison.Ordinal);
Assert.Contains("currentIndex < playlist.Count", safeTailAdmission,
StringComparison.Ordinal);
Assert.Contains("playlist.Count(row => string.Equals(", safeTailAdmission,
StringComparison.Ordinal);
var orderedTailDelete = Slice(
Window,
"else if (IsOrderedPlaylistTailMutationIntent(intent))",
"else if (IsQueuedLocalSelectionIntent(intent))");
Assert.Contains("_orderedPlaylistTailMutationIntentGate.WaitAsync(",
orderedTailDelete, StringComparison.Ordinal);
Assert.Contains("0,", orderedTailDelete, StringComparison.Ordinal);
Assert.Contains("_orderedLocalSelectionIntentGate.WaitAsync(",
orderedTailDelete, StringComparison.Ordinal);
Assert.Contains("await _intentGate.WaitAsync", orderedTailDelete,
StringComparison.Ordinal);
Assert.Contains(
"intent is LegacyDeleteSelectedPlaylistRowsIntent",
Window,
StringComparison.Ordinal);
Assert.Contains("_orderedPlaylistTailMutationIntentGate.Release();",
Window,
StringComparison.Ordinal);
}
private static void AssertKnownFailureDoesNotQuarantine(

View File

@@ -0,0 +1,196 @@
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
public sealed class LegacyProgramNamedPlaylistPersistenceContractTests
{
private static readonly string Playout = File.ReadAllText(Path.Combine(
FindRepositoryRoot(),
"src",
"MBN_STOCK_WEBVIEW.LegacyParityApp",
"MainWindow.Playout.cs"));
private static readonly string Window = File.ReadAllText(Path.Combine(
FindRepositoryRoot(),
"src",
"MBN_STOCK_WEBVIEW.LegacyParityApp",
"MainWindow.xaml.cs"));
private static readonly string Web = File.ReadAllText(Path.Combine(
FindRepositoryRoot(),
"src",
"MBN_STOCK_WEBVIEW.LegacyParityApp",
"Web",
"app.js"));
[Fact]
public void ProgramAllowsNamedPlaylistWritesWithoutOpeningDbLoadReplacement()
{
var admission = Slice(
Playout,
"private bool CanAcceptOperatorMutation(",
"private static bool RequiresIdlePlaylistMutation");
var safeWrites = Slice(
admission,
"var isSafeNamedPlaylistPersistenceMutation",
"var refreshAllowsMutation");
Assert.Contains("LegacyCreateNamedPlaylistIntent", safeWrites,
StringComparison.Ordinal);
Assert.Contains("LegacySaveCurrentNamedPlaylistIntent", safeWrites,
StringComparison.Ordinal);
Assert.Contains("LegacySaveCurrentNamedPlaylistToIntent", safeWrites,
StringComparison.Ordinal);
Assert.Contains("LegacyDeleteSelectedNamedPlaylistIntent", safeWrites,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacyRefreshNamedPlaylistsIntent", safeWrites,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacyLoadSelectedNamedPlaylistIntent", safeWrites,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacyLoadNamedPlaylistByIdIntent", safeWrites,
StringComparison.Ordinal);
Assert.Contains("isSafeNamedPlaylistPersistenceMutation ||", admission,
StringComparison.Ordinal);
var idleOnly = Slice(
Playout,
"private static bool RequiresIdlePlaylistMutation",
"private static bool IsPlaylistMutationIdle");
Assert.Contains("LegacyLoadSelectedNamedPlaylistIntent", idleOnly,
StringComparison.Ordinal);
Assert.Contains("LegacyLoadNamedPlaylistByIdIntent", idleOnly,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacyCreateNamedPlaylistIntent", idleOnly,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacySaveCurrentNamedPlaylistIntent", idleOnly,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacySaveCurrentNamedPlaylistToIntent", idleOnly,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacyDeleteSelectedNamedPlaylistIntent", idleOnly,
StringComparison.Ordinal);
}
[Fact]
public void NamedPlaylistModalRequestsUseOnePendingSlotBehindRefresh()
{
Assert.Contains(
"private readonly SemaphoreSlim _orderedNamedPlaylistIntentGate = new(1, 1);",
Window,
StringComparison.Ordinal);
var process = Slice(
Window,
"private async Task ProcessIntentAsync",
"private LegacyOperatorSnapshot SelectCutWithoutAppend");
var queued = Slice(
process,
"else if (IsNamedPlaylistModalIntent(intent))",
"// Keep the zero-timeout admission used by database and edit intents;");
Assert.Contains("_orderedNamedPlaylistIntentGate.WaitAsync(", queued,
StringComparison.Ordinal);
Assert.Contains("0,", queued, StringComparison.Ordinal);
Assert.Contains("await _intentGate.WaitAsync", queued,
StringComparison.Ordinal);
Assert.Contains("_orderedNamedPlaylistIntentGate.Release();", process,
StringComparison.Ordinal);
var classifier = Slice(
Window,
"private static bool IsNamedPlaylistModalIntent",
"private LegacyOperatorSnapshot ReportIntentFailure");
foreach (var intent in new[]
{
"LegacyRefreshNamedPlaylistsIntent",
"LegacySelectNamedPlaylistIntent",
"LegacyLoadSelectedNamedPlaylistIntent",
"LegacyLoadNamedPlaylistByIdIntent",
"LegacyCreateNamedPlaylistIntent",
"LegacySaveCurrentNamedPlaylistIntent",
"LegacySaveCurrentNamedPlaylistToIntent",
"LegacyDeleteSelectedNamedPlaylistIntent",
"LegacyDismissDialogIntent"
})
{
Assert.Contains(intent, classifier, StringComparison.Ordinal);
}
Assert.Contains("else if (intent is LegacyDismissDialogIntent)", process,
StringComparison.Ordinal);
Assert.Contains("PostState(_controller.Current);", process,
StringComparison.Ordinal);
}
[Fact]
public void OneButtonResultDialogCannotLoseItsClickToBackgroundBusyState()
{
Assert.Contains(
"dialogOk.disabled = legacyDialogIsConfirmation && state.isBusy === true;",
Web,
StringComparison.Ordinal);
}
[Fact]
public void NamedPlaylistConfirmationReenablesBothActionsWhenItOpens()
{
var open = Slice(
Web,
"function openNamedPlaylistConfirmation",
"function restoreNamedPlaylistAfterConfirmation");
Assert.Contains("if (!options || isNamedPlaylistActionLocked()) return false;",
open,
StringComparison.Ordinal);
Assert.Contains("namedPlaylistConfirmationYes.disabled = false;",
open,
StringComparison.Ordinal);
Assert.Contains("namedPlaylistConfirmationNo.disabled = false;",
open,
StringComparison.Ordinal);
}
[Fact]
public void OpenNamedPlaylistConfirmationDoesNotLoseClickToProgramRefresh()
{
var render = Slice(
Web,
"function renderNamedPlaylist",
"function clearOperatorCatalogDragVisuals");
var confirmationLock = Slice(
render,
"namedPlaylistConfirmationYes.disabled =",
"if (deferredSelectionAction)");
Assert.Contains("pendingNamedPlaylistCommand !== null", confirmationLock,
StringComparison.Ordinal);
Assert.Contains("namedPlaylistState.isWriteInProgress === true", confirmationLock,
StringComparison.Ordinal);
Assert.DoesNotContain("operatorMutationLocked", confirmationLock,
StringComparison.Ordinal);
Assert.DoesNotContain("uiBusy", confirmationLock,
StringComparison.Ordinal);
}
private static string Slice(string source, string startMarker, string endMarker)
{
var start = source.IndexOf(startMarker, StringComparison.Ordinal);
var end = source.IndexOf(endMarker, start + startMarker.Length,
StringComparison.Ordinal);
Assert.True(start >= 0, $"Missing source marker: {startMarker}");
Assert.True(end > start, $"Missing source marker: {endMarker}");
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("Could not locate the repository root.");
}
}

View File

@@ -216,6 +216,17 @@ if (($actualRoutes -join ',') -cne ($expectedRoutes -join ',')) {
throw 'The 30-route validation order changed.'
}
$probeFixedIdBlock = [regex]::Match(
$stage,
'const fixedIds = new Set\(\[(?<body>.*?)\]\);',
[Text.RegularExpressions.RegexOptions]::Singleline)
if (-not $probeFixedIdBlock.Success) { throw 'The probe fixed-action allowlist is missing.' }
foreach ($actionId in @('fixed-072', 'fixed-073', 'fixed-074', 'fixed-075')) {
if (-not $probeFixedIdBlock.Groups['body'].Value.Contains("'$actionId'")) {
throw "The S6001 physical action is not probe-allowlisted: $actionId"
}
}
foreach ($token in @(
'S6001_ASSET_INDEPENDENT_ACTIONS',
"actionId: 'fixed-072'",
@@ -226,6 +237,12 @@ foreach ($token in @(
"subtype: 'WtiCrude'",
"subtype: 'BrentCrude'",
"subtype: 'Gold'",
"physicalSelectCatalogTab(client, 'overseas')",
'#market-tabs button[data-tab-id=',
'current.probe.nativeStateCount > nativeStateBefore',
"current.dom.activeWorkspace === 'catalog'",
'current.dom.catalogVisible === true',
"types[0] !== 'select-tab'",
'physicalDoubleClickAndWait(client, selector',
"current.state.currentCode === '6001'")) {
if (-not $stage.Contains($token)) {