| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:ffi'; |
| |
| import 'package:async/async.dart'; |
| import 'package:cli_util/src/windows/win32_stdin.dart'; |
| import 'package:test/test.dart'; |
| |
| void main() { |
| late MockWin32Console mockConsole; |
| |
| setUp(() { |
| mockConsole = MockWin32Console(); |
| Win32Console.instance = mockConsole; |
| }); |
| |
| tearDown(() { |
| Win32Console.instance = null; |
| }); |
| |
| test( |
| 'Win32AnsiStdin translates windows stdin events to ansi escape codes', |
| () async { |
| final stdin = Win32AnsiStdin(); |
| final queue = StreamQueue(stdin); |
| |
| // Test a regular character 'a' (ASCII 97). |
| await mockConsole.pushEvent(97); |
| expect(await queue.next, [97]); |
| |
| // Test special virtual key codes. |
| await mockConsole.pushEvent(VirtualKeyCodes.up); |
| expect(await queue.next, [0x1b, 0x5b, 0x41]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.down); |
| expect(await queue.next, [0x1b, 0x5b, 0x42]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.right); |
| expect(await queue.next, [0x1b, 0x5b, 0x43]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.left); |
| expect(await queue.next, [0x1b, 0x5b, 0x44]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.home); |
| expect(await queue.next, [0x1b, 0x5b, 0x48]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.end); |
| expect(await queue.next, [0x1b, 0x5b, 0x46]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.pageUp); |
| expect(await queue.next, [0x1b, 0x5b, 0x35, 0x7e]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.pageDown); |
| expect(await queue.next, [0x1b, 0x5b, 0x36, 0x7e]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.enter); |
| expect(await queue.next, [0x0d]); |
| |
| await mockConsole.pushEvent(VirtualKeyCodes.escape); |
| expect(await queue.next, [0x1b]); |
| |
| // Test Ctrl+A (Select All) |
| await mockConsole.pushEvent(0x41, controlKeyState: 0x0008); |
| expect(await queue.next, [1]); |
| |
| await queue.cancel(); |
| }, |
| ); |
| } |
| |
| class MockWin32Console extends Win32Console { |
| final List<({int keyCode, int controlKeyState})> _mockEvents; |
| |
| factory MockWin32Console() => MockWin32Console._([]); |
| |
| MockWin32Console._(List<({int keyCode, int controlKeyState})> mockEvents) |
| : _mockEvents = mockEvents, |
| super.internal( |
| (nStdHandle) => 123, |
| (hConsoleInput, lpBuffer, nLength, lpNumberOfEventsRead) { |
| final count = |
| nLength < mockEvents.length ? nLength : mockEvents.length; |
| for (var i = 0; i < count; i++) { |
| final event = mockEvents.removeAt(0); |
| final record = (lpBuffer + i).ref; |
| record.eventType = InputRecordEventType.keyEvent; |
| record.event.keyEvent.bKeyDown = 1; |
| record.event.keyEvent.wVirtualKeyCode = event.keyCode; |
| record.event.keyEvent.uChar = 0; |
| record.event.keyEvent.dwControlKeyState = event.controlKeyState; |
| if (event.keyCode >= 32 && event.keyCode < 127) { |
| record.event.keyEvent.uChar = event.keyCode; |
| } |
| } |
| lpNumberOfEventsRead.value = count; |
| return 1; |
| }, |
| (hConsoleInput, lpcNumberOfEvents) { |
| lpcNumberOfEvents.value = mockEvents.length; |
| return 1; |
| }, |
| ); |
| |
| Future<void> pushEvent(int keyCode, {int controlKeyState = 0}) async { |
| _mockEvents.add((keyCode: keyCode, controlKeyState: controlKeyState)); |
| // Yield to allow the event loop in Win32AnsiStdin to run. |
| await Future<void>.delayed(const Duration(milliseconds: 100)); |
| } |
| } |