feat: complete legacy parity and modernize operator UI

This commit is contained in:
2026-07-22 12:34:46 +09:00
parent fc4007d676
commit 939c252d23
149 changed files with 26515 additions and 1736 deletions

View File

@@ -11,6 +11,8 @@ public sealed class LegacyPlayoutNativeContractTests
Path.Combine(AppRoot, "MainWindow.Playout.cs"));
private static readonly string Window = File.ReadAllText(
Path.Combine(AppRoot, "MainWindow.xaml.cs"));
private static readonly string BackgroundPicker = File.ReadAllText(
Path.Combine(AppRoot, "MainWindow.BackgroundPicker.cs"));
private static readonly string Project = File.ReadAllText(
Path.Combine(AppRoot, "MBN_STOCK_WEBVIEW.LegacyParityApp.csproj"));
@@ -115,6 +117,44 @@ public sealed class LegacyPlayoutNativeContractTests
StringComparison.Ordinal);
}
[Fact]
public void LocalSelectionAndDoubleClickGesturesQueueBehindRefreshInArrivalOrder()
{
var process = Normalize(Slice(
Window,
"private async Task ProcessIntentAsync(LegacyUiIntent intent)",
"private LegacyOperatorSnapshot SelectCutWithoutAppend("));
var classifier = Slice(
Window,
"private static bool IsQueuedLocalSelectionIntent",
"private static bool IsOperatorSettingsIntent");
Assert.Contains("_orderedLocalSelectionIntentGate.WaitAsync(", process,
StringComparison.Ordinal);
Assert.Contains("else if (IsQueuedLocalSelectionIntent(intent))", process,
StringComparison.Ordinal);
Assert.Contains("await _intentGate.WaitAsync(_lifetimeCancellation.Token)", process,
StringComparison.Ordinal);
Assert.Contains("_orderedLocalSelectionIntentGate.Release();", process,
StringComparison.Ordinal);
Assert.Contains("LegacySelectStockIntent", classifier, StringComparison.Ordinal);
Assert.Contains("LegacySelectOperatorCatalogStockIntent", classifier,
StringComparison.Ordinal);
Assert.Contains("LegacyActivateOperatorCatalogStockIntent", classifier,
StringComparison.Ordinal);
Assert.Contains("LegacyCutPointerDownIntent", 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);
}
[Fact]
public void AutomaticRefreshUsesTheCoreSchedulerAndGenerationOwnedState()
{
@@ -307,6 +347,96 @@ public sealed class LegacyPlayoutNativeContractTests
StringComparison.Ordinal);
}
[Fact]
public void BackgroundControlsStageTheNextCueWithoutChangingTheOnAirRefreshSource()
{
Assert.Contains("_stagedCompositionOptions", Playout, StringComparison.Ordinal);
Assert.Contains(
"_stagedCompositionOptions = new MutableLegacySceneCueCompositionOptionsSource(\n" +
" initialComposition);",
Normalize(Playout),
StringComparison.Ordinal);
var toggle = Slice(
Playout,
"private async Task<LegacyOperatorSnapshot> ToggleBackgroundAsync(",
"private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(");
Assert.Contains("if (!CanStageBackground())", toggle, StringComparison.Ordinal);
Assert.Contains("var source = _stagedCompositionOptions!;", toggle,
StringComparison.Ordinal);
Assert.DoesNotContain("var source = _compositionOptions!;", toggle,
StringComparison.Ordinal);
var stageGuard = Slice(
Playout,
"private bool CanStageBackground()",
"private void ApplyStagedCompositionForNextCue()");
Assert.Contains("status.State != PlayoutConnectionState.OutcomeUnknown", stageGuard,
StringComparison.Ordinal);
Assert.Contains("!status.IsPlayCompletionPending", stageGuard,
StringComparison.Ordinal);
Assert.Contains("!status.IsTakeOutCompletionPending", stageGuard,
StringComparison.Ordinal);
Assert.DoesNotContain("PreparedSceneName", stageGuard, StringComparison.Ordinal);
Assert.DoesNotContain("OnAirSceneName", stageGuard, StringComparison.Ordinal);
Assert.DoesNotContain("refresh.IsActive", stageGuard, StringComparison.Ordinal);
var execute = Slice(
Playout,
"private async Task<LegacyOperatorSnapshot> ExecuteOperatorPlayoutAsync(",
"private async Task<LegacyOperatorPlayoutRequest> CreateFreshPagedPlayoutRequestAsync(");
var stopRefresh = RequiredIndex(execute, "StopRefreshLoop();");
var promote = RequiredIndex(execute, "ApplyStagedCompositionForNextCue();");
var dispatch = RequiredIndex(execute, "switch (command)");
Assert.True(stopRefresh < promote && promote < dispatch);
Assert.Contains("LegacyOperatorPlayoutCommand.Prepare or", execute,
StringComparison.Ordinal);
Assert.Contains("LegacyOperatorPlayoutCommand.TakeIn or", execute,
StringComparison.Ordinal);
Assert.Contains("LegacyOperatorPlayoutCommand.Next", execute,
StringComparison.Ordinal);
var promotion = Slice(
Playout,
"private void ApplyStagedCompositionForNextCue()",
"private bool CanAcceptOperatorMutation(");
Assert.Contains("active.Update(staged.Current);", promotion,
StringComparison.Ordinal);
Assert.Contains("var composition = _stagedCompositionOptions?.Current", Playout,
StringComparison.Ordinal);
Assert.Contains("CanStageBackground(),", Playout, StringComparison.Ordinal);
}
[Fact]
public void F2PickerStartsInsideTheValidatedBackgroundRootAndKeepsClosedExtensions()
{
var choose = Slice(
Playout,
"private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(",
"private bool CanChangeComposition()");
Assert.Contains("TryGetTrustedBackgroundDirectory(", choose,
StringComparison.Ordinal);
Assert.Contains("PickBackgroundFileAsync(\n trustedBackgroundDirectory)",
Normalize(choose), StringComparison.Ordinal);
Assert.DoesNotContain("PickerLocationId.PicturesLibrary", Playout,
StringComparison.Ordinal);
Assert.Contains("dialog.SetFolder(initialFolder);", BackgroundPicker,
StringComparison.Ordinal);
Assert.Contains("dialog.SetDefaultFolder(initialFolder);", BackgroundPicker,
StringComparison.Ordinal);
Assert.Contains("*.vrv;*.jpg;*.jpeg;*.png", BackgroundPicker,
StringComparison.Ordinal);
Assert.Contains("FileOpenOptions.ForceFileSystem", BackgroundPicker,
StringComparison.Ordinal);
Assert.Contains("FileOpenOptions.NoDereferenceLinks", BackgroundPicker,
StringComparison.Ordinal);
Assert.Contains("WindowNative.GetWindowHandle(this)", BackgroundPicker,
StringComparison.Ordinal);
Assert.Contains("DispatcherQueue.HasThreadAccess", BackgroundPicker,
StringComparison.Ordinal);
}
[Fact]
public void HomeAndEndStopAutomaticRefreshBeforeChangingTheCandidateRow()
{
@@ -334,9 +464,23 @@ public sealed class LegacyPlayoutNativeContractTests
}
[Fact]
public void PreparedProgramBlocksCutAppendButAllowsPureCutSelection()
public void ProgramAllowsOriginalEnabledGesturesAndSafeTailAppendButKeepsStructuralEditsLocked()
{
Assert.Contains("CanAcceptOperatorMutation()", Window, StringComparison.Ordinal);
Assert.Contains("CanAcceptOperatorMutation(intent, out var mutationRejectionMessage)",
Window, StringComparison.Ordinal);
Assert.Contains("CanAcceptOperatorMutation(", Playout, StringComparison.Ordinal);
Assert.Contains("RequiresIdlePlaylistMutation(intent)", Playout,
StringComparison.Ordinal);
Assert.Contains("CanDeleteSelectedPlaylistTail(status, workflow)", Playout,
StringComparison.Ordinal);
Assert.Contains("isSafeCutTailAppend", Playout, StringComparison.Ordinal);
Assert.Contains("isSafeProgramEnabledStateMutation", Playout,
StringComparison.Ordinal);
Assert.Contains("refreshAllowsMutation", Playout, StringComparison.Ordinal);
Assert.Contains("!status.IsPlayCompletionPending", Playout, StringComparison.Ordinal);
Assert.Contains("!status.IsTakeOutCompletionPending", Playout, StringComparison.Ordinal);
Assert.Contains("status.State != PlayoutConnectionState.OutcomeUnknown", Playout,
StringComparison.Ordinal);
Assert.Contains("IsOperatorMutationQuarantined()", Window,
StringComparison.Ordinal);
Assert.Contains("ObserveOperatorMutationQuarantine(state)", Window,
@@ -351,6 +495,44 @@ public sealed class LegacyPlayoutNativeContractTests
StringComparison.Ordinal);
Assert.Contains("LegacyConfirmManualListIntent", Playout,
StringComparison.Ordinal);
Assert.Contains("CanMutatePlaylistEnabledState(status, workflow)", Playout,
StringComparison.Ordinal);
var idleOnlyMutations = Slice(
Playout,
"private static bool RequiresIdlePlaylistMutation",
"private static bool IsPlaylistMutationIdle");
Assert.Contains("LegacySetPlaylistEnabledIntent", idleOnlyMutations,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacySetAllPlaylistEnabledIntent", idleOnlyMutations,
StringComparison.Ordinal);
Assert.DoesNotContain("LegacyToggleActivePlaylistEnabledIntent", idleOnlyMutations,
StringComparison.Ordinal);
var programEnabledAdmission = Slice(
Playout,
"private bool CanMutatePlaylistEnabledState(",
"private static bool IsOperatorMutationIntent");
Assert.Contains("status.OnAirSceneName", programEnabledAdmission,
StringComparison.Ordinal);
Assert.Contains("workflow.OnAirCutCode", programEnabledAdmission,
StringComparison.Ordinal);
Assert.Contains("workflow.CurrentEntryId", programEnabledAdmission,
StringComparison.Ordinal);
Assert.Contains("workflow.CurrentCueIndexZeroBased", programEnabledAdmission,
StringComparison.Ordinal);
Assert.Contains("IsPlaylistMutationIdle(status, workflow)", programEnabledAdmission,
StringComparison.Ordinal);
var nextKindProjection = Slice(
Playout,
"private LegacyWorkflowNextKind ResolveOperatorNextKind(",
"private LegacyOperatorPlayoutSnapshot CreatePlayoutSnapshot()");
Assert.Contains("workflow.IsLastPage", nextKindProjection,
StringComparison.Ordinal);
Assert.Contains("LegacyWorkflowNextKind.PageNext", nextKindProjection,
StringComparison.Ordinal);
Assert.Contains("Skip(currentIndex + 1)", nextKindProjection,
StringComparison.Ordinal);
Assert.Contains("Any(static row => row.IsEnabled)", nextKindProjection,
StringComparison.Ordinal);
var mutationClassifier = Slice(
Playout,
"private static bool IsOperatorMutationIntent",
@@ -363,6 +545,15 @@ public sealed class LegacyPlayoutNativeContractTests
StringComparison.Ordinal);
Assert.Contains("pointer.AllowAppend", Window,
StringComparison.Ordinal);
Assert.Equal(2, Window.Split(
"PostState(SelectCutWithoutAppend(cutPointer));",
StringSplitOptions.None).Length - 1);
var selectionOnly = Slice(
Window,
"private LegacyOperatorSnapshot SelectCutWithoutAppend(",
"private static bool IsOrderedPlayoutIntent");
Assert.Contains("allowAppend: false", selectionOnly,
StringComparison.Ordinal);
}
private static void AssertKnownFailureDoesNotQuarantine(