namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests; public sealed class LegacyCutKeyboardParityTests { [Fact] public async Task ControlNavigationMovesOnlyFocusAndShiftKeepsTheNativeAnchor() { var controller = await CreateReadyControllerAsync(); controller.CutPointerDown(2, false, false, 1, 47, 1_000); var controlDown = controller.CutKeyDown( LegacyCutKeyboardKey.ArrowDown, control: true, shift: false); var shiftSpace = controller.CutKeyDown( LegacyCutKeyboardKey.Space, control: false, shift: true); var plainDown = controller.CutKeyDown( LegacyCutKeyboardKey.ArrowDown, control: false, shift: false); var shiftUp = controller.CutKeyDown( LegacyCutKeyboardKey.ArrowUp, control: false, shift: true); AssertCutState(controlDown, [2], focused: 3); AssertCutState(shiftSpace, [2, 3], focused: 3); AssertCutState(plainDown, [4], focused: 4); AssertCutState(shiftUp, [3, 4], focused: 3); } [Fact] public async Task ControlShiftNavigationAddsRangeWithoutDiscardingExistingSelection() { var controller = await CreateReadyControllerAsync(); controller.CutPointerDown(1, false, false, 1, 24, 1_000); controller.CutPointerDown(4, true, false, 1, 93, 2_000); controller.CutKeyDown( LegacyCutKeyboardKey.ArrowUp, control: true, shift: false); var state = controller.CutKeyDown( LegacyCutKeyboardKey.ArrowUp, control: true, shift: true); AssertCutState(state, [1, 2, 3, 4], focused: 2); } [Fact] public async Task HomeAndEndUsePhysicalBoundariesIncludingSeparatorRows() { var controller = await CreateReadyControllerAsync(); controller.CutPointerDown(2, false, false, 1, 47, 1_000); var end = controller.CutKeyDown( LegacyCutKeyboardKey.End, control: false, shift: false); var controlHome = controller.CutKeyDown( LegacyCutKeyboardKey.Home, control: true, shift: false); var shiftSpace = controller.CutKeyDown( LegacyCutKeyboardKey.Space, control: false, shift: true); var last = end.CutRows.Count - 1; AssertCutState(end, [last], focused: last); AssertCutState(controlHome, [last], focused: 0); AssertCutState(shiftSpace, Enumerable.Range(0, last + 1), focused: 0); } [Fact] public async Task ControlSpaceTogglesFocusedRowAndMovesTheAnchor() { var controller = await CreateReadyControllerAsync(); controller.CutPointerDown(1, false, false, 1, 24, 1_000); var toggledOff = controller.CutKeyDown( LegacyCutKeyboardKey.Space, control: true, shift: false); controller.CutKeyDown( LegacyCutKeyboardKey.ArrowDown, control: true, shift: false); var extended = controller.CutKeyDown( LegacyCutKeyboardKey.Space, control: false, shift: true); AssertCutState(toggledOff, [], focused: 1); AssertCutState(extended, [1, 2], focused: 2); } [Fact] public async Task KeyboardSelectionFeedsTheSameNativeDropAuthority() { var controller = await CreateReadyControllerAsync(); var selected = controller.CutKeyDown( LegacyCutKeyboardKey.Space, control: false, shift: false); var dropped = controller.DropSelectedCutsOnPlaylist(); AssertCutState(selected, [0], focused: 0); Assert.Equal( LegacyStockCutCatalog.Rows[0].RawLabel, Assert.Single(dropped.Playlist).RawCutLabel); } [Fact] public void KeyboardSelectionIsIgnoredUntilAStockIsSelected() { var controller = new LegacyOperatorController(new OneStockLookup()); var before = controller.Current; var after = controller.CutKeyDown( LegacyCutKeyboardKey.End, control: false, shift: false); Assert.Equal(before.Revision, after.Revision); Assert.DoesNotContain(after.CutRows, row => row.IsFocused || row.IsSelected); } private static async Task CreateReadyControllerAsync() { var controller = new LegacyOperatorController(new OneStockLookup()); await controller.SearchStocksAsync("stock", LegacySearchTrigger.Button); controller.SelectStock(0); return controller; } private static void AssertCutState( LegacyOperatorSnapshot snapshot, IEnumerable selected, int focused) { Assert.Equal( selected, snapshot.CutRows .Where(row => row.IsSelected) .Select(row => row.PhysicalIndex)); Assert.Equal( focused, Assert.Single(snapshot.CutRows, row => row.IsFocused).PhysicalIndex); } private sealed class OneStockLookup : ILegacyStockLookup { public Task SearchAsync( string query, CancellationToken cancellationToken = default) => Task.FromResult(new LegacyStockSearchData( query, ["Stock"], [new LegacyStockIdentity( LegacyDomesticStockMarket.Kospi, "Stock", "000001")])); } }