// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';

import 'semantics_tester.dart';

void main() {
  group(LogicalKeySet, () {
    test('LogicalKeySet passes parameters correctly.', () {
      final LogicalKeySet set1 = LogicalKeySet(LogicalKeyboardKey.keyA);
      final LogicalKeySet set2 = LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB);
      final LogicalKeySet set3 = LogicalKeySet(
        LogicalKeyboardKey.keyA,
        LogicalKeyboardKey.keyB,
        LogicalKeyboardKey.keyC,
      );
      final LogicalKeySet set4 = LogicalKeySet(
        LogicalKeyboardKey.keyA,
        LogicalKeyboardKey.keyB,
        LogicalKeyboardKey.keyC,
        LogicalKeyboardKey.keyD,
      );
      final LogicalKeySet setFromSet = LogicalKeySet.fromSet(<LogicalKeyboardKey>{
        LogicalKeyboardKey.keyA,
        LogicalKeyboardKey.keyB,
        LogicalKeyboardKey.keyC,
        LogicalKeyboardKey.keyD,
      });
      expect(set1.keys, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.keyA}));
      expect(
        set2.keys,
        equals(<LogicalKeyboardKey>{LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB}),
      );
      expect(
        set3.keys,
        equals(<LogicalKeyboardKey>{
          LogicalKeyboardKey.keyA,
          LogicalKeyboardKey.keyB,
          LogicalKeyboardKey.keyC,
        }),
      );
      expect(
        set4.keys,
        equals(<LogicalKeyboardKey>{
          LogicalKeyboardKey.keyA,
          LogicalKeyboardKey.keyB,
          LogicalKeyboardKey.keyC,
          LogicalKeyboardKey.keyD,
        }),
      );
      expect(
        setFromSet.keys,
        equals(<LogicalKeyboardKey>{
          LogicalKeyboardKey.keyA,
          LogicalKeyboardKey.keyB,
          LogicalKeyboardKey.keyC,
          LogicalKeyboardKey.keyD,
        }),
      );
    });

    test('LogicalKeySet works as a map key.', () {
      final LogicalKeySet set1 = LogicalKeySet(LogicalKeyboardKey.keyA);
      final LogicalKeySet set2 = LogicalKeySet(
        LogicalKeyboardKey.keyA,
        LogicalKeyboardKey.keyB,
        LogicalKeyboardKey.keyC,
        LogicalKeyboardKey.keyD,
      );
      final LogicalKeySet set3 = LogicalKeySet(
        LogicalKeyboardKey.keyD,
        LogicalKeyboardKey.keyC,
        LogicalKeyboardKey.keyB,
        LogicalKeyboardKey.keyA,
      );
      final LogicalKeySet set4 = LogicalKeySet.fromSet(<LogicalKeyboardKey>{
        LogicalKeyboardKey.keyD,
        LogicalKeyboardKey.keyC,
        LogicalKeyboardKey.keyB,
        LogicalKeyboardKey.keyA,
      });
      final Map<LogicalKeySet, String> map = <LogicalKeySet, String>{set1: 'one'};
      expect(set2 == set3, isTrue);
      expect(set2 == set4, isTrue);
      expect(set2.hashCode, set3.hashCode);
      expect(set2.hashCode, set4.hashCode);
      expect(map.containsKey(set1), isTrue);
      expect(map.containsKey(LogicalKeySet(LogicalKeyboardKey.keyA)), isTrue);
      expect(
        set2,
        equals(
          LogicalKeySet.fromSet(<LogicalKeyboardKey>{
            LogicalKeyboardKey.keyA,
            LogicalKeyboardKey.keyB,
            LogicalKeyboardKey.keyC,
            LogicalKeyboardKey.keyD,
          }),
        ),
      );
    });

    testWidgets('handles two keys', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(LogicalKeySet(LogicalKeyboardKey.keyC, LogicalKeyboardKey.control), (
          Intent intent,
        ) {
          invoked += 1;
        }),
      );
      await tester.pump();

      // LCtrl -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 1);
      invoked = 0;

      // KeyC -> LCtrl: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 1);
      invoked = 0;

      // RCtrl -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
      expect(invoked, 1);
      invoked = 0;

      // LCtrl -> LShift -> KeyC: Reject
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 0);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 0);
      invoked = 0;

      // LCtrl -> KeyA -> KeyC: Reject
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 0);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(invoked, 0);
      invoked = 0;

      expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
    });

    test('LogicalKeySet.hashCode is stable', () {
      final LogicalKeySet set1 = LogicalKeySet(LogicalKeyboardKey.keyA);
      expect(set1.hashCode, set1.hashCode);

      final LogicalKeySet set2 = LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB);
      expect(set2.hashCode, set2.hashCode);

      final LogicalKeySet set3 = LogicalKeySet(
        LogicalKeyboardKey.keyA,
        LogicalKeyboardKey.keyB,
        LogicalKeyboardKey.keyC,
      );
      expect(set3.hashCode, set3.hashCode);

      final LogicalKeySet set4 = LogicalKeySet(
        LogicalKeyboardKey.keyA,
        LogicalKeyboardKey.keyB,
        LogicalKeyboardKey.keyC,
        LogicalKeyboardKey.keyD,
      );
      expect(set4.hashCode, set4.hashCode);
    });

    test('LogicalKeySet.hashCode is order-independent', () {
      expect(
        LogicalKeySet(LogicalKeyboardKey.keyA).hashCode,
        LogicalKeySet(LogicalKeyboardKey.keyA).hashCode,
      );
      expect(
        LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB).hashCode,
        LogicalKeySet(LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyA).hashCode,
      );
      expect(
        LogicalKeySet(
          LogicalKeyboardKey.keyA,
          LogicalKeyboardKey.keyB,
          LogicalKeyboardKey.keyC,
        ).hashCode,
        LogicalKeySet(
          LogicalKeyboardKey.keyC,
          LogicalKeyboardKey.keyB,
          LogicalKeyboardKey.keyA,
        ).hashCode,
      );
      expect(
        LogicalKeySet(
          LogicalKeyboardKey.keyA,
          LogicalKeyboardKey.keyB,
          LogicalKeyboardKey.keyC,
          LogicalKeyboardKey.keyD,
        ).hashCode,
        LogicalKeySet(
          LogicalKeyboardKey.keyD,
          LogicalKeyboardKey.keyC,
          LogicalKeyboardKey.keyB,
          LogicalKeyboardKey.keyA,
        ).hashCode,
      );
    });

    testWidgets('isActivatedBy works as expected', (WidgetTester tester) async {
      // Collect some key events to use for testing.
      final List<KeyEvent> events = <KeyEvent>[];
      await tester.pumpWidget(
        Focus(
          autofocus: true,
          onKeyEvent: (FocusNode node, KeyEvent event) {
            events.add(event);
            return KeyEventResult.ignored;
          },
          child: const SizedBox(),
        ),
      );

      final LogicalKeySet set = LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.control);

      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(set, events.last), isTrue);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(set, events.last), isTrue);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(set, events.last), isFalse);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(ShortcutActivator.isActivatedBy(set, events.last), isFalse);
    });

    test('LogicalKeySet diagnostics work.', () {
      final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

      LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB).debugFillProperties(builder);

      final List<String> description = builder.properties
          .where((DiagnosticsNode node) {
            return !node.isFiltered(DiagnosticLevel.info);
          })
          .map((DiagnosticsNode node) => node.toString())
          .toList();

      expect(description.length, equals(1));
      expect(description[0], equals('keys: Key A + Key B'));
    });
  });

  group(SingleActivator, () {
    testWidgets('handles Ctrl-C', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(const SingleActivator(LogicalKeyboardKey.keyC, control: true), (
          Intent intent,
        ) {
          invoked += 1;
        }),
      );
      await tester.pump();

      // LCtrl -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 1);
      invoked = 0;

      // KeyC -> LCtrl: Reject
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      invoked = 0;

      // LShift -> LCtrl -> KeyC: Reject
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 0);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      invoked = 0;

      // With Ctrl-C pressed, KeyA -> Release KeyA: Reject
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      invoked = 0;
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(invoked, 0);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      invoked = 0;

      // LCtrl -> KeyA -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      invoked = 0;

      // RCtrl -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
      expect(invoked, 1);
      invoked = 0;

      // LCtrl -> RCtrl -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
      expect(invoked, 1);
      invoked = 0;

      // While holding Ctrl-C, press KeyA: Reject
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 1);
      invoked = 0;

      expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
    }, variant: KeySimulatorTransitModeVariant.all());

    testWidgets('handles repeated events', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(const SingleActivator(LogicalKeyboardKey.keyC, control: true), (
          Intent intent,
        ) {
          invoked += 1;
        }),
      );
      await tester.pump();

      // LCtrl -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 2);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 2);
      invoked = 0;

      expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
    }, variant: KeySimulatorTransitModeVariant.all());

    testWidgets('rejects repeated events if requested', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(
          const SingleActivator(LogicalKeyboardKey.keyC, control: true, includeRepeats: false),
          (Intent intent) {
            invoked += 1;
          },
        ),
      );
      await tester.pump();

      // LCtrl -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 1);
      invoked = 0;

      expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
    }, variant: KeySimulatorTransitModeVariant.all());

    testWidgets('handles Shift-Ctrl-C', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(
          const SingleActivator(LogicalKeyboardKey.keyC, shift: true, control: true),
          (Intent intent) {
            invoked += 1;
          },
        ),
      );
      await tester.pump();

      // LShift -> LCtrl -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 1);
      invoked = 0;

      // LCtrl -> LShift -> KeyC: Accept
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 1);
      invoked = 0;

      // LCtrl -> KeyC -> LShift: Reject
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 0);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 0);
      invoked = 0;

      expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
    });

    testWidgets('isActivatedBy works as expected', (WidgetTester tester) async {
      // Collect some key events to use for testing.
      final List<KeyEvent> events = <KeyEvent>[];
      await tester.pumpWidget(
        Focus(
          autofocus: true,
          onKeyEvent: (FocusNode node, KeyEvent event) {
            events.add(event);
            return KeyEventResult.ignored;
          },
          child: const SizedBox(),
        ),
      );

      const SingleActivator singleActivator = SingleActivator(
        LogicalKeyboardKey.keyA,
        control: true,
      );

      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);

      const SingleActivator noRepeatSingleActivator = SingleActivator(
        LogicalKeyboardKey.keyA,
        control: true,
        includeRepeats: false,
      );

      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isTrue);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
    });

    testWidgets('numLock works as expected when set to LockState.locked', (
      WidgetTester tester,
    ) async {
      // Collect some key events to use for testing.
      final List<KeyEvent> events = <KeyEvent>[];
      await tester.pumpWidget(
        Focus(
          autofocus: true,
          onKeyEvent: (FocusNode node, KeyEvent event) {
            events.add(event);
            return KeyEventResult.ignored;
          },
          child: const SizedBox(),
        ),
      );

      const SingleActivator singleActivator = SingleActivator(
        LogicalKeyboardKey.numpad4,
        numLock: LockState.locked,
      );

      // Lock NumLock.
      await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
      expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isTrue);

      await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);

      await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);

      // Unlock NumLock.
      await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
      expect(
        HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock),
        isFalse,
      );

      await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);

      await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
    });

    testWidgets('numLock works as expected when set to LockState.unlocked', (
      WidgetTester tester,
    ) async {
      // Collect some key events to use for testing.
      final List<KeyEvent> events = <KeyEvent>[];
      await tester.pumpWidget(
        Focus(
          autofocus: true,
          onKeyEvent: (FocusNode node, KeyEvent event) {
            events.add(event);
            return KeyEventResult.ignored;
          },
          child: const SizedBox(),
        ),
      );

      const SingleActivator singleActivator = SingleActivator(
        LogicalKeyboardKey.numpad4,
        numLock: LockState.unlocked,
      );

      // Lock NumLock.
      await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
      expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isTrue);

      await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);

      await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);

      // Unlock NumLock.
      await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
      expect(
        HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock),
        isFalse,
      );

      await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);

      await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
    });

    testWidgets('numLock works as expected when set to LockState.ignored', (
      WidgetTester tester,
    ) async {
      // Collect some key events to use for testing.
      final List<KeyEvent> events = <KeyEvent>[];
      await tester.pumpWidget(
        Focus(
          autofocus: true,
          onKeyEvent: (FocusNode node, KeyEvent event) {
            events.add(event);
            return KeyEventResult.ignored;
          },
          child: const SizedBox(),
        ),
      );

      const SingleActivator singleActivator = SingleActivator(LogicalKeyboardKey.numpad4);

      // Lock NumLock.
      await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
      expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isTrue);

      await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);

      await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);

      // Unlock NumLock.
      await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
      expect(
        HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock),
        isFalse,
      );

      await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
      expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);

      await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
    });

    group('diagnostics.', () {
      test('single key', () {
        final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

        const SingleActivator(LogicalKeyboardKey.keyA).debugFillProperties(builder);

        final List<String> description = builder.properties
            .where((DiagnosticsNode node) {
              return !node.isFiltered(DiagnosticLevel.info);
            })
            .map((DiagnosticsNode node) => node.toString())
            .toList();

        expect(description.length, equals(1));
        expect(description[0], equals('keys: Key A'));
      });

      test('no repeats', () {
        final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

        const SingleActivator(
          LogicalKeyboardKey.keyA,
          includeRepeats: false,
        ).debugFillProperties(builder);

        final List<String> description = builder.properties
            .where((DiagnosticsNode node) {
              return !node.isFiltered(DiagnosticLevel.info);
            })
            .map((DiagnosticsNode node) => node.toString())
            .toList();

        expect(description.length, equals(2));
        expect(description[0], equals('keys: Key A'));
        expect(description[1], equals('excluding repeats'));
      });

      test('combination', () {
        final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

        const SingleActivator(
          LogicalKeyboardKey.keyA,
          control: true,
          shift: true,
          alt: true,
          meta: true,
        ).debugFillProperties(builder);

        final List<String> description = builder.properties
            .where((DiagnosticsNode node) {
              return !node.isFiltered(DiagnosticLevel.info);
            })
            .map((DiagnosticsNode node) => node.toString())
            .toList();

        expect(description.length, equals(1));
        expect(description[0], equals('keys: Control + Alt + Meta + Shift + Key A'));
      });
    });
  });

  group(Shortcuts, () {
    testWidgets('Default constructed Shortcuts has empty shortcuts', (WidgetTester tester) async {
      const Shortcuts shortcuts = Shortcuts(
        shortcuts: <LogicalKeySet, Intent>{},
        child: SizedBox(),
      );
      await tester.pumpWidget(shortcuts);
      expect(shortcuts.shortcuts, isNotNull);
      expect(shortcuts.shortcuts, isEmpty);
    });

    testWidgets('Default constructed Shortcuts.manager has empty shortcuts', (
      WidgetTester tester,
    ) async {
      final ShortcutManager manager = ShortcutManager();
      addTearDown(manager.dispose);
      expect(manager.shortcuts, isNotNull);
      expect(manager.shortcuts, isEmpty);
      final Shortcuts shortcuts = Shortcuts.manager(manager: manager, child: const SizedBox());
      await tester.pumpWidget(shortcuts);
      expect(shortcuts.shortcuts, isNotNull);
      expect(shortcuts.shortcuts, isEmpty);
    });

    testWidgets('Shortcuts.manager passes on shortcuts', (WidgetTester tester) async {
      final Map<LogicalKeySet, Intent> testShortcuts = <LogicalKeySet, Intent>{
        LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
      };
      final ShortcutManager manager = ShortcutManager(shortcuts: testShortcuts);
      addTearDown(manager.dispose);
      expect(manager.shortcuts, isNotNull);
      expect(manager.shortcuts, equals(testShortcuts));
      final Shortcuts shortcuts = Shortcuts.manager(manager: manager, child: const SizedBox());
      await tester.pumpWidget(shortcuts);
      expect(shortcuts.shortcuts, isNotNull);
      expect(shortcuts.shortcuts, equals(testShortcuts));
    });

    testWidgets('ShortcutManager handles shortcuts', (WidgetTester tester) async {
      final GlobalKey containerKey = GlobalKey();
      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
      final TestShortcutManager testManager = TestShortcutManager(
        pressedKeys,
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
        },
      );
      addTearDown(testManager.dispose);
      bool invoked = false;
      await tester.pumpWidget(
        Actions(
          actions: <Type, Action<Intent>>{
            TestIntent: TestAction(
              onInvoke: (Intent intent) {
                invoked = true;
                return true;
              },
            ),
          },
          child: Shortcuts.manager(
            manager: testManager,
            child: Focus(
              autofocus: true,
              child: SizedBox(key: containerKey, width: 100, height: 100),
            ),
          ),
        ),
      );
      await tester.pump();
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, isTrue);
      expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft]));
    });

    testWidgets('Shortcuts.manager lets manager handle shortcuts', (WidgetTester tester) async {
      final GlobalKey containerKey = GlobalKey();
      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
      final TestShortcutManager testManager = TestShortcutManager(
        pressedKeys,
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
        },
      );
      addTearDown(testManager.dispose);
      bool invoked = false;
      await tester.pumpWidget(
        Actions(
          actions: <Type, Action<Intent>>{
            TestIntent: TestAction(
              onInvoke: (Intent intent) {
                invoked = true;
                return true;
              },
            ),
          },
          child: Shortcuts.manager(
            manager: testManager,
            child: Focus(
              autofocus: true,
              child: SizedBox(key: containerKey, width: 100, height: 100),
            ),
          ),
        ),
      );
      await tester.pump();
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, isTrue);
      expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft]));
    });

    testWidgets('ShortcutManager ignores key presses with no primary focus', (
      WidgetTester tester,
    ) async {
      final GlobalKey containerKey = GlobalKey();
      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
      final TestShortcutManager testManager = TestShortcutManager(
        pressedKeys,
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
        },
      );
      addTearDown(testManager.dispose);
      bool invoked = false;
      await tester.pumpWidget(
        Actions(
          actions: <Type, Action<Intent>>{
            TestIntent: TestAction(
              onInvoke: (Intent intent) {
                invoked = true;
                return true;
              },
            ),
          },
          child: Shortcuts.manager(
            manager: testManager,
            child: SizedBox(key: containerKey, width: 100, height: 100),
          ),
        ),
      );
      await tester.pump();
      expect(primaryFocus, isNull);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, isFalse);
      expect(pressedKeys, isEmpty);
    });

    test('$ShortcutManager dispatches object creation in constructor', () async {
      await expectLater(
        await memoryEvents(() => ShortcutManager().dispose(), ShortcutManager),
        areCreateAndDispose,
      );
    });

    testWidgets("Shortcuts passes to the next Shortcuts widget if it doesn't map the key", (
      WidgetTester tester,
    ) async {
      final GlobalKey containerKey = GlobalKey();
      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
      final TestShortcutManager testManager = TestShortcutManager(
        pressedKeys,
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
        },
      );
      addTearDown(testManager.dispose);
      bool invoked = false;
      await tester.pumpWidget(
        Shortcuts.manager(
          manager: testManager,
          child: Actions(
            actions: <Type, Action<Intent>>{
              TestIntent: TestAction(
                onInvoke: (Intent intent) {
                  invoked = true;
                  return invoked;
                },
              ),
            },
            child: Shortcuts(
              shortcuts: <LogicalKeySet, Intent>{
                LogicalKeySet(LogicalKeyboardKey.keyA): Intent.doNothing,
              },
              child: Focus(
                autofocus: true,
                child: SizedBox(key: containerKey, width: 100, height: 100),
              ),
            ),
          ),
        ),
      );
      await tester.pump();
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, isTrue);
      expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft]));
    });

    testWidgets('Shortcuts can disable a shortcut with Intent.doNothing', (
      WidgetTester tester,
    ) async {
      final GlobalKey containerKey = GlobalKey();
      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
      final TestShortcutManager testManager = TestShortcutManager(
        pressedKeys,
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
        },
      );
      addTearDown(testManager.dispose);
      bool invoked = false;
      await tester.pumpWidget(
        MaterialApp(
          home: Shortcuts.manager(
            manager: testManager,
            child: Actions(
              actions: <Type, Action<Intent>>{
                TestIntent: TestAction(
                  onInvoke: (Intent intent) {
                    invoked = true;
                    return invoked;
                  },
                ),
              },
              child: Shortcuts(
                shortcuts: <LogicalKeySet, Intent>{
                  LogicalKeySet(LogicalKeyboardKey.shift): Intent.doNothing,
                },
                child: Focus(
                  autofocus: true,
                  child: SizedBox(key: containerKey, width: 100, height: 100),
                ),
              ),
            ),
          ),
        ),
      );
      await tester.pump();
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, isFalse);
      expect(pressedKeys, isEmpty);
    });

    testWidgets(
      "Shortcuts that aren't bound to an action don't absorb keys meant for text fields",
      (WidgetTester tester) async {
        final GlobalKey textFieldKey = GlobalKey();
        final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
        final TestShortcutManager testManager = TestShortcutManager(
          pressedKeys,
          shortcuts: <LogicalKeySet, Intent>{
            LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
          },
        );
        addTearDown(testManager.dispose);
        await tester.pumpWidget(
          MaterialApp(
            home: Material(
              child: Shortcuts.manager(
                manager: testManager,
                child: TextField(key: textFieldKey, autofocus: true),
              ),
            ),
          ),
        );
        await tester.pump();
        final bool handled = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
        expect(handled, isFalse);
        expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.keyA]));
      },
    );

    testWidgets('Shortcuts that are bound to an action do override text fields', (
      WidgetTester tester,
    ) async {
      final GlobalKey textFieldKey = GlobalKey();
      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
      final TestShortcutManager testManager = TestShortcutManager(
        pressedKeys,
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
        },
      );
      addTearDown(testManager.dispose);
      bool invoked = false;
      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: Shortcuts.manager(
              manager: testManager,
              child: Actions(
                actions: <Type, Action<Intent>>{
                  TestIntent: TestAction(
                    onInvoke: (Intent intent) {
                      invoked = true;
                      return invoked;
                    },
                  ),
                },
                child: TextField(key: textFieldKey, autofocus: true),
              ),
            ),
          ),
        ),
      );
      await tester.pump();
      final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
      expect(result, isTrue);
      expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.keyA]));
      expect(invoked, isTrue);
    });

    testWidgets('Shortcuts can override intents that apply to text fields', (
      WidgetTester tester,
    ) async {
      final GlobalKey textFieldKey = GlobalKey();
      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
      final TestShortcutManager testManager = TestShortcutManager(
        pressedKeys,
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
        },
      );
      addTearDown(testManager.dispose);
      bool invoked = false;
      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: Shortcuts.manager(
              manager: testManager,
              child: Actions(
                actions: <Type, Action<Intent>>{
                  TestIntent: TestAction(
                    onInvoke: (Intent intent) {
                      invoked = true;
                      return invoked;
                    },
                  ),
                },
                child: Actions(
                  actions: <Type, Action<Intent>>{TestIntent: DoNothingAction(consumesKey: false)},
                  child: TextField(key: textFieldKey, autofocus: true),
                ),
              ),
            ),
          ),
        ),
      );
      await tester.pump();
      final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
      expect(result, isFalse);
      expect(invoked, isFalse);
    });

    testWidgets(
      'Shortcuts can override intents that apply to text fields with DoNothingAndStopPropagationIntent',
      (WidgetTester tester) async {
        final GlobalKey textFieldKey = GlobalKey();
        final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
        final TestShortcutManager testManager = TestShortcutManager(
          pressedKeys,
          shortcuts: <LogicalKeySet, Intent>{
            LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
          },
        );
        addTearDown(testManager.dispose);
        bool invoked = false;
        await tester.pumpWidget(
          MaterialApp(
            home: Material(
              child: Shortcuts.manager(
                manager: testManager,
                child: Actions(
                  actions: <Type, Action<Intent>>{
                    TestIntent: TestAction(
                      onInvoke: (Intent intent) {
                        invoked = true;
                        return invoked;
                      },
                    ),
                  },
                  child: Shortcuts(
                    shortcuts: <LogicalKeySet, Intent>{
                      LogicalKeySet(LogicalKeyboardKey.keyA):
                          const DoNothingAndStopPropagationIntent(),
                    },
                    child: TextField(key: textFieldKey, autofocus: true),
                  ),
                ),
              ),
            ),
          ),
        );
        await tester.pump();
        final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
        expect(result, isFalse);
        expect(invoked, isFalse);
      },
    );

    test('Shortcuts diagnostics work.', () {
      final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

      Shortcuts(
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.keyA): const ActivateIntent(),
          LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight):
              const DirectionalFocusIntent(TraversalDirection.right),
        },
        child: const SizedBox(),
      ).debugFillProperties(builder);

      final List<String> description = builder.properties
          .where((DiagnosticsNode node) {
            return !node.isFiltered(DiagnosticLevel.info);
          })
          .map((DiagnosticsNode node) => node.toString())
          .toList();

      expect(description.length, equals(1));
      expect(
        description[0],
        equalsIgnoringHashCodes(
          'shortcuts: {{Shift + Key A}: ActivateIntent#00000, {Shift + Arrow Right}: DirectionalFocusIntent#00000(direction: right)}',
        ),
      );
    });

    test('Shortcuts diagnostics work when debugLabel specified.', () {
      final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

      Shortcuts(
        debugLabel: '<Debug Label>',
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB): const ActivateIntent(),
        },
        child: const SizedBox(),
      ).debugFillProperties(builder);

      final List<String> description = builder.properties
          .where((DiagnosticsNode node) {
            return !node.isFiltered(DiagnosticLevel.info);
          })
          .map((DiagnosticsNode node) => node.toString())
          .toList();

      expect(description.length, equals(1));
      expect(description[0], equals('shortcuts: <Debug Label>'));
    });

    test('Shortcuts diagnostics work when manager not specified.', () {
      final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

      Shortcuts(
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB): const ActivateIntent(),
        },
        child: const SizedBox(),
      ).debugFillProperties(builder);

      final List<String> description = builder.properties
          .where((DiagnosticsNode node) {
            return !node.isFiltered(DiagnosticLevel.info);
          })
          .map((DiagnosticsNode node) => node.toString())
          .toList();

      expect(description.length, equals(1));
      expect(
        description[0],
        equalsIgnoringHashCodes('shortcuts: {{Key A + Key B}: ActivateIntent#00000}'),
      );
    });

    test('Shortcuts diagnostics work when manager specified.', () {
      final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
      final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
      final TestShortcutManager testManager = TestShortcutManager(
        pressedKeys,
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB): const ActivateIntent(),
        },
      );
      addTearDown(testManager.dispose);

      Shortcuts.manager(manager: testManager, child: const SizedBox()).debugFillProperties(builder);

      final List<String> description = builder.properties
          .where((DiagnosticsNode node) {
            return !node.isFiltered(DiagnosticLevel.info);
          })
          .map((DiagnosticsNode node) => node.toString())
          .toList();

      expect(description.length, equals(2));
      expect(
        description[0],
        equalsIgnoringHashCodes(
          'manager: TestShortcutManager#00000(shortcuts: {LogicalKeySet#00000(keys: Key A + Key B): ActivateIntent#00000})',
        ),
      );
      expect(
        description[1],
        equalsIgnoringHashCodes('shortcuts: {{Key A + Key B}: ActivateIntent#00000}'),
      );
    });

    testWidgets('Shortcuts support multiple intents', (WidgetTester tester) async {
      tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
      bool? value = true;
      Widget buildApp() {
        return MaterialApp(
          shortcuts: <LogicalKeySet, Intent>{
            LogicalKeySet(LogicalKeyboardKey.space): const PrioritizedIntents(
              orderedIntents: <Intent>[
                ActivateIntent(),
                ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
              ],
            ),
            LogicalKeySet(LogicalKeyboardKey.tab): const NextFocusIntent(),
            LogicalKeySet(LogicalKeyboardKey.pageUp): const ScrollIntent(
              direction: AxisDirection.up,
              type: ScrollIncrementType.page,
            ),
          },
          home: Material(
            child: Center(
              child: ListView(
                primary: true,
                children: <Widget>[
                  StatefulBuilder(
                    builder: (BuildContext context, StateSetter setState) {
                      return Checkbox(
                        value: value,
                        onChanged: (bool? newValue) => setState(() {
                          value = newValue;
                        }),
                        focusColor: Colors.orange[500],
                      );
                    },
                  ),
                  Container(color: Colors.blue, height: 1000),
                ],
              ),
            ),
          ),
        );
      }

      await tester.pumpWidget(buildApp());
      await tester.pumpAndSettle();
      expect(
        tester.binding.focusManager.primaryFocus!.toStringShort(),
        equalsIgnoringHashCodes(
          'FocusScopeNode#00000(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])',
        ),
      );
      final ScrollController controller = PrimaryScrollController.of(
        tester.element(find.byType(ListView)),
      );
      expect(controller.position.pixels, 0.0);
      expect(value, isTrue);

      await tester.sendKeyEvent(LogicalKeyboardKey.space);
      await tester.pumpAndSettle();
      // ScrollView scrolls
      expect(controller.position.pixels, 448.0);
      expect(value, isTrue);

      await tester.sendKeyEvent(LogicalKeyboardKey.pageUp);
      await tester.pumpAndSettle();
      await tester.sendKeyEvent(LogicalKeyboardKey.tab);
      await tester.pumpAndSettle();
      // Focus is now on the checkbox.
      expect(
        tester.binding.focusManager.primaryFocus!.toStringShort(),
        equalsIgnoringHashCodes('FocusNode#00000([PRIMARY FOCUS])'),
      );
      expect(value, isTrue);
      expect(controller.position.pixels, 0.0);

      await tester.sendKeyEvent(LogicalKeyboardKey.space);
      await tester.pumpAndSettle();
      // Checkbox is toggled, scroll view does not scroll.
      expect(value, isFalse);
      expect(controller.position.pixels, 0.0);

      await tester.sendKeyEvent(LogicalKeyboardKey.space);
      await tester.pumpAndSettle();
      expect(value, isTrue);
      expect(controller.position.pixels, 0.0);
    });

    testWidgets('Shortcuts support activators that returns null in triggers', (
      WidgetTester tester,
    ) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(
          const DumbLogicalActivator(LogicalKeyboardKey.keyC),
          (Intent intent) {
            invoked += 1;
          },
          const SingleActivator(LogicalKeyboardKey.keyC, control: true),
          (Intent intent) {
            invoked += 10;
          },
        ),
      );
      await tester.pump();

      // Press KeyC: Accepted by DumbLogicalActivator
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      invoked = 0;

      // Press ControlLeft + KeyC: Accepted by SingleActivator
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 10);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 10);
      invoked = 0;

      // Press ControlLeft + ShiftLeft + KeyC: Accepted by DumbLogicalActivator
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 1);
      invoked = 0;
    });

    testWidgets('Shortcuts does not insert a semantics node when includeSemantics is false', (
      WidgetTester tester,
    ) async {
      final SemanticsTester semanticsTester = SemanticsTester(tester);
      addTearDown(semanticsTester.dispose);

      // By default, includeSemantics is true.
      await tester.pumpWidget(
        const Shortcuts(shortcuts: <LogicalKeySet, Intent>{}, child: SizedBox()),
      );

      expect(
        semanticsTester,
        hasSemantics(
          TestSemantics.root(children: <TestSemantics>[TestSemantics(id: 1)]),
          ignoreRect: true,
          ignoreTransform: true,
        ),
      );

      await tester.pumpWidget(
        const Shortcuts(
          includeSemantics: false,
          shortcuts: <LogicalKeySet, Intent>{},
          child: SizedBox(),
        ),
      );

      expect(
        semanticsTester,
        hasSemantics(TestSemantics.root(), ignoreRect: true, ignoreTransform: true),
      );

      semanticsTester.dispose();
    });
  });

  group('CharacterActivator', () {
    testWidgets('is triggered on events with correct character', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(const CharacterActivator('?'), (Intent intent) {
          invoked += 1;
        }),
      );
      await tester.pump();

      // Press Shift + /
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 1);
      invoked = 0;
    }, variant: KeySimulatorTransitModeVariant.all());

    testWidgets('handles repeated events', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(const CharacterActivator('?'), (Intent intent) {
          invoked += 1;
        }),
      );
      await tester.pump();

      // Press KeyC: Accepted by DumbLogicalActivator
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
      expect(invoked, 1);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.slash, character: '?');
      expect(invoked, 2);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 2);
      invoked = 0;
    }, variant: KeySimulatorTransitModeVariant.all());

    testWidgets('rejects repeated events if requested', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(const CharacterActivator('?', includeRepeats: false), (Intent intent) {
          invoked += 1;
        }),
      );
      await tester.pump();

      // Press Shift + /
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
      expect(invoked, 1);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.slash, character: '?');
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      expect(invoked, 1);
      invoked = 0;
    }, variant: KeySimulatorTransitModeVariant.all());

    testWidgets('handles Alt, Ctrl and Meta', (WidgetTester tester) async {
      int invoked = 0;
      await tester.pumpWidget(
        activatorTester(const CharacterActivator('?', alt: true, meta: true, control: true), (
          Intent intent,
        ) {
          invoked += 1;
        }),
      );
      await tester.pump();

      // Press Shift + /
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
      await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
      expect(invoked, 0);

      // Press Left Alt + Ctrl + Meta + Shift + /
      await tester.sendKeyDownEvent(LogicalKeyboardKey.altLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.metaLeft);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.metaLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.altLeft);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      expect(invoked, 1);
      invoked = 0;

      // Press Right Alt + Ctrl + Meta + Shift + /
      await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftRight);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.altRight);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.metaRight);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight);
      expect(invoked, 0);
      await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
      expect(invoked, 1);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftRight);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.metaRight);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.altRight);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
      expect(invoked, 1);
      invoked = 0;
    }, variant: KeySimulatorTransitModeVariant.all());

    testWidgets('isActivatedBy works as expected', (WidgetTester tester) async {
      // Collect some key events to use for testing.
      final List<KeyEvent> events = <KeyEvent>[];
      await tester.pumpWidget(
        Focus(
          autofocus: true,
          onKeyEvent: (FocusNode node, KeyEvent event) {
            events.add(event);
            return KeyEventResult.ignored;
          },
          child: const SizedBox(),
        ),
      );

      const CharacterActivator characterActivator = CharacterActivator('a');

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isTrue);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isTrue);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isFalse);

      const CharacterActivator noRepeatCharacterActivator = CharacterActivator(
        'a',
        includeRepeats: false,
      );

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isTrue);
      await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isFalse);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isFalse);
    });

    group('diagnostics.', () {
      test('single key', () {
        final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

        const CharacterActivator('A').debugFillProperties(builder);

        final List<String> description = builder.properties
            .where((DiagnosticsNode node) {
              return !node.isFiltered(DiagnosticLevel.info);
            })
            .map((DiagnosticsNode node) => node.toString())
            .toList();

        expect(description.length, equals(1));
        expect(description[0], equals("character: 'A'"));
      });

      test('no repeats', () {
        final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

        const CharacterActivator('A', includeRepeats: false).debugFillProperties(builder);

        final List<String> description = builder.properties
            .where((DiagnosticsNode node) {
              return !node.isFiltered(DiagnosticLevel.info);
            })
            .map((DiagnosticsNode node) => node.toString())
            .toList();

        expect(description.length, equals(2));
        expect(description[0], equals("character: 'A'"));
        expect(description[1], equals('excluding repeats'));
      });

      test('combination', () {
        final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();

        const CharacterActivator('A', control: true, meta: true).debugFillProperties(builder);

        final List<String> description = builder.properties
            .where((DiagnosticsNode node) {
              return !node.isFiltered(DiagnosticLevel.info);
            })
            .map((DiagnosticsNode node) => node.toString())
            .toList();

        expect(description.length, equals(1));
        expect(description[0], equals("character: Control + Meta + 'A'"));
      });
    });
  });

  group('CallbackShortcuts', () {
    testWidgets('trigger on key events', (WidgetTester tester) async {
      int invokedA = 0;
      int invokedB = 0;
      await tester.pumpWidget(
        CallbackShortcuts(
          bindings: <ShortcutActivator, VoidCallback>{
            const SingleActivator(LogicalKeyboardKey.keyA): () {
              invokedA += 1;
            },
            const SingleActivator(LogicalKeyboardKey.keyB): () {
              invokedB += 1;
            },
          },
          child: const Focus(autofocus: true, child: Placeholder()),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      await tester.pump();
      expect(invokedA, equals(1));
      expect(invokedB, equals(0));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(invokedA, equals(1));
      expect(invokedB, equals(0));
      invokedA = 0;
      invokedB = 0;
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyB);
      expect(invokedA, equals(0));
      expect(invokedB, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyB);
      expect(invokedA, equals(0));
      expect(invokedB, equals(1));
    });

    testWidgets('nested CallbackShortcuts stop propagation', (WidgetTester tester) async {
      int invokedOuter = 0;
      int invokedInner = 0;
      await tester.pumpWidget(
        CallbackShortcuts(
          bindings: <ShortcutActivator, VoidCallback>{
            const SingleActivator(LogicalKeyboardKey.keyA): () {
              invokedOuter += 1;
            },
          },
          child: CallbackShortcuts(
            bindings: <ShortcutActivator, VoidCallback>{
              const SingleActivator(LogicalKeyboardKey.keyA): () {
                invokedInner += 1;
              },
            },
            child: const Focus(autofocus: true, child: Placeholder()),
          ),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(invokedOuter, equals(0));
      expect(invokedInner, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(invokedOuter, equals(0));
      expect(invokedInner, equals(1));
    });

    testWidgets('non-overlapping nested CallbackShortcuts fire appropriately', (
      WidgetTester tester,
    ) async {
      int invokedOuter = 0;
      int invokedInner = 0;
      await tester.pumpWidget(
        CallbackShortcuts(
          bindings: <ShortcutActivator, VoidCallback>{
            const CharacterActivator('b'): () {
              invokedOuter += 1;
            },
          },
          child: CallbackShortcuts(
            bindings: <ShortcutActivator, VoidCallback>{
              const CharacterActivator('a'): () {
                invokedInner += 1;
              },
            },
            child: const Focus(autofocus: true, child: Placeholder()),
          ),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(invokedOuter, equals(0));
      expect(invokedInner, equals(1));
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyB);
      expect(invokedOuter, equals(1));
      expect(invokedInner, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyB);
      expect(invokedOuter, equals(1));
      expect(invokedInner, equals(1));
    });

    testWidgets('Works correctly with Shortcuts too', (WidgetTester tester) async {
      int invokedCallbackA = 0;
      int invokedCallbackB = 0;
      int invokedActionA = 0;
      int invokedActionB = 0;

      void clear() {
        invokedCallbackA = 0;
        invokedCallbackB = 0;
        invokedActionA = 0;
        invokedActionB = 0;
      }

      await tester.pumpWidget(
        Actions(
          actions: <Type, Action<Intent>>{
            TestIntent: TestAction(
              onInvoke: (Intent intent) {
                invokedActionA += 1;
                return true;
              },
            ),
            TestIntent2: TestAction(
              onInvoke: (Intent intent) {
                invokedActionB += 1;
                return true;
              },
            ),
          },
          child: CallbackShortcuts(
            bindings: <ShortcutActivator, VoidCallback>{
              const CharacterActivator('b'): () {
                invokedCallbackB += 1;
              },
            },
            child: Shortcuts(
              shortcuts: <LogicalKeySet, Intent>{
                LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
                LogicalKeySet(LogicalKeyboardKey.keyB): const TestIntent2(),
              },
              child: CallbackShortcuts(
                bindings: <ShortcutActivator, VoidCallback>{
                  const CharacterActivator('a'): () {
                    invokedCallbackA += 1;
                  },
                },
                child: const Focus(autofocus: true, child: Placeholder()),
              ),
            ),
          ),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(invokedCallbackA, equals(1));
      expect(invokedCallbackB, equals(0));
      expect(invokedActionA, equals(0));
      expect(invokedActionB, equals(0));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      clear();
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyB);
      expect(invokedCallbackA, equals(0));
      expect(invokedCallbackB, equals(0));
      expect(invokedActionA, equals(0));
      expect(invokedActionB, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyB);
    });
  });

  group('ShortcutRegistrar', () {
    testWidgets('trigger ShortcutRegistrar on key events', (WidgetTester tester) async {
      int invokedA = 0;
      int invokedB = 0;
      await tester.pumpWidget(
        ShortcutRegistrar(
          child: TestCallbackRegistration(
            shortcuts: <ShortcutActivator, Intent>{
              const SingleActivator(LogicalKeyboardKey.keyA): VoidCallbackIntent(() {
                invokedA += 1;
              }),
              const SingleActivator(LogicalKeyboardKey.keyB): VoidCallbackIntent(() {
                invokedB += 1;
              }),
            },
            child: Actions(
              actions: <Type, Action<Intent>>{VoidCallbackIntent: VoidCallbackAction()},
              child: const Focus(autofocus: true, child: Placeholder()),
            ),
          ),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      await tester.pump();
      expect(invokedA, equals(1));
      expect(invokedB, equals(0));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(invokedA, equals(1));
      expect(invokedB, equals(0));
      invokedA = 0;
      invokedB = 0;
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyB);
      expect(invokedA, equals(0));
      expect(invokedB, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyB);
      expect(invokedA, equals(0));
      expect(invokedB, equals(1));
    });

    testWidgets('MaterialApp has a ShortcutRegistrar listening', (WidgetTester tester) async {
      int invokedA = 0;
      int invokedB = 0;
      await tester.pumpWidget(
        MaterialApp(
          home: TestCallbackRegistration(
            shortcuts: <ShortcutActivator, Intent>{
              const SingleActivator(LogicalKeyboardKey.keyA): VoidCallbackIntent(() {
                invokedA += 1;
              }),
              const SingleActivator(LogicalKeyboardKey.keyB): VoidCallbackIntent(() {
                invokedB += 1;
              }),
            },
            child: Actions(
              actions: <Type, Action<Intent>>{VoidCallbackIntent: VoidCallbackAction()},
              child: const Focus(autofocus: true, child: Placeholder()),
            ),
          ),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      await tester.pump();
      expect(invokedA, equals(1));
      expect(invokedB, equals(0));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(invokedA, equals(1));
      expect(invokedB, equals(0));
      invokedA = 0;
      invokedB = 0;
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyB);
      expect(invokedA, equals(0));
      expect(invokedB, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyB);
      expect(invokedA, equals(0));
      expect(invokedB, equals(1));
    });

    testWidgets("doesn't override text field shortcuts", (WidgetTester tester) async {
      final TextEditingController controller = TextEditingController();
      addTearDown(controller.dispose);
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: ShortcutRegistrar(
              child: TestCallbackRegistration(
                shortcuts: const <ShortcutActivator, Intent>{
                  SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(
                    SelectionChangedCause.keyboard,
                  ),
                },
                child: TextField(autofocus: true, controller: controller),
              ),
            ),
          ),
        ),
      );
      controller.text = 'Testing';
      await tester.pump();

      // Send a "Ctrl-A", which should be bound to select all by default.
      await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
      await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
      await tester.pump();

      expect(controller.selection.baseOffset, equals(0));
      expect(controller.selection.extentOffset, equals(7));
    });

    testWidgets('nested ShortcutRegistrars stop propagation', (WidgetTester tester) async {
      int invokedOuter = 0;
      int invokedInner = 0;
      await tester.pumpWidget(
        ShortcutRegistrar(
          child: TestCallbackRegistration(
            shortcuts: <ShortcutActivator, Intent>{
              const SingleActivator(LogicalKeyboardKey.keyA): VoidCallbackIntent(() {
                invokedOuter += 1;
              }),
            },
            child: ShortcutRegistrar(
              child: TestCallbackRegistration(
                shortcuts: <ShortcutActivator, Intent>{
                  const SingleActivator(LogicalKeyboardKey.keyA): VoidCallbackIntent(() {
                    invokedInner += 1;
                  }),
                },
                child: Actions(
                  actions: <Type, Action<Intent>>{VoidCallbackIntent: VoidCallbackAction()},
                  child: const Focus(autofocus: true, child: Placeholder()),
                ),
              ),
            ),
          ),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(invokedOuter, equals(0));
      expect(invokedInner, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      expect(invokedOuter, equals(0));
      expect(invokedInner, equals(1));
    });

    testWidgets('non-overlapping nested ShortcutRegistrars fire appropriately', (
      WidgetTester tester,
    ) async {
      int invokedOuter = 0;
      int invokedInner = 0;
      await tester.pumpWidget(
        ShortcutRegistrar(
          child: TestCallbackRegistration(
            shortcuts: <ShortcutActivator, Intent>{
              const CharacterActivator('b'): VoidCallbackIntent(() {
                invokedOuter += 1;
              }),
            },
            child: ShortcutRegistrar(
              child: TestCallbackRegistration(
                shortcuts: <ShortcutActivator, Intent>{
                  const CharacterActivator('a'): VoidCallbackIntent(() {
                    invokedInner += 1;
                  }),
                },
                child: Actions(
                  actions: <Type, Action<Intent>>{VoidCallbackIntent: VoidCallbackAction()},
                  child: const Focus(autofocus: true, child: Placeholder()),
                ),
              ),
            ),
          ),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(invokedOuter, equals(0));
      expect(invokedInner, equals(1));
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyB);
      expect(invokedOuter, equals(1));
      expect(invokedInner, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyB);
      expect(invokedOuter, equals(1));
      expect(invokedInner, equals(1));
    });

    testWidgets('Works correctly with Shortcuts too', (WidgetTester tester) async {
      int invokedCallbackA = 0;
      int invokedCallbackB = 0;
      int invokedActionA = 0;
      int invokedActionB = 0;

      void clear() {
        invokedCallbackA = 0;
        invokedCallbackB = 0;
        invokedActionA = 0;
        invokedActionB = 0;
      }

      await tester.pumpWidget(
        Actions(
          actions: <Type, Action<Intent>>{
            TestIntent: TestAction(
              onInvoke: (Intent intent) {
                invokedActionA += 1;
                return true;
              },
            ),
            TestIntent2: TestAction(
              onInvoke: (Intent intent) {
                invokedActionB += 1;
                return true;
              },
            ),
            VoidCallbackIntent: VoidCallbackAction(),
          },
          child: ShortcutRegistrar(
            child: TestCallbackRegistration(
              shortcuts: <ShortcutActivator, Intent>{
                const CharacterActivator('b'): VoidCallbackIntent(() {
                  invokedCallbackB += 1;
                }),
              },
              child: Shortcuts(
                shortcuts: const <ShortcutActivator, Intent>{
                  SingleActivator(LogicalKeyboardKey.keyA): TestIntent(),
                  SingleActivator(LogicalKeyboardKey.keyB): TestIntent2(),
                },
                child: ShortcutRegistrar(
                  child: TestCallbackRegistration(
                    shortcuts: <ShortcutActivator, Intent>{
                      const CharacterActivator('a'): VoidCallbackIntent(() {
                        invokedCallbackA += 1;
                      }),
                    },
                    child: const Focus(autofocus: true, child: Placeholder()),
                  ),
                ),
              ),
            ),
          ),
        ),
      );
      await tester.pump();

      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
      expect(invokedCallbackA, equals(1));
      expect(invokedCallbackB, equals(0));
      expect(invokedActionA, equals(0));
      expect(invokedActionB, equals(0));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
      clear();
      await tester.sendKeyDownEvent(LogicalKeyboardKey.keyB);
      expect(invokedCallbackA, equals(0));
      expect(invokedCallbackB, equals(0));
      expect(invokedActionA, equals(0));
      expect(invokedActionB, equals(1));
      await tester.sendKeyUpEvent(LogicalKeyboardKey.keyB);
    });

    testWidgets('Updating shortcuts triggers dependency rebuild', (WidgetTester tester) async {
      final List<Map<ShortcutActivator, Intent>> shortcutsChanged =
          <Map<ShortcutActivator, Intent>>[];
      void dependenciesUpdated(Map<ShortcutActivator, Intent> shortcuts) {
        shortcutsChanged.add(shortcuts);
      }

      await tester.pumpWidget(
        ShortcutRegistrar(
          child: TestCallbackRegistration(
            onDependencyUpdate: dependenciesUpdated,
            shortcuts: const <ShortcutActivator, Intent>{
              SingleActivator(LogicalKeyboardKey.keyA): SelectAllTextIntent(
                SelectionChangedCause.keyboard,
              ),
              SingleActivator(LogicalKeyboardKey.keyB): ActivateIntent(),
            },
            child: Actions(
              actions: <Type, Action<Intent>>{VoidCallbackIntent: VoidCallbackAction()},
              child: const Focus(autofocus: true, child: Placeholder()),
            ),
          ),
        ),
      );

      await tester.pumpWidget(
        ShortcutRegistrar(
          child: TestCallbackRegistration(
            onDependencyUpdate: dependenciesUpdated,
            shortcuts: const <ShortcutActivator, Intent>{
              SingleActivator(LogicalKeyboardKey.keyA): SelectAllTextIntent(
                SelectionChangedCause.keyboard,
              ),
            },
            child: Actions(
              actions: <Type, Action<Intent>>{VoidCallbackIntent: VoidCallbackAction()},
              child: const Focus(autofocus: true, child: Placeholder()),
            ),
          ),
        ),
      );

      await tester.pumpWidget(
        ShortcutRegistrar(
          child: TestCallbackRegistration(
            onDependencyUpdate: dependenciesUpdated,
            shortcuts: const <ShortcutActivator, Intent>{
              SingleActivator(LogicalKeyboardKey.keyA): SelectAllTextIntent(
                SelectionChangedCause.keyboard,
              ),
              SingleActivator(LogicalKeyboardKey.keyB): ActivateIntent(),
            },
            child: Actions(
              actions: <Type, Action<Intent>>{VoidCallbackIntent: VoidCallbackAction()},
              child: const Focus(autofocus: true, child: Placeholder()),
            ),
          ),
        ),
      );

      expect(shortcutsChanged.length, equals(2));
      expect(
        shortcutsChanged.last,
        equals(const <ShortcutActivator, Intent>{
          SingleActivator(LogicalKeyboardKey.keyA): SelectAllTextIntent(
            SelectionChangedCause.keyboard,
          ),
          SingleActivator(LogicalKeyboardKey.keyB): ActivateIntent(),
        }),
      );
    });

    testWidgets('using a disposed token asserts', (WidgetTester tester) async {
      final ShortcutRegistry registry = ShortcutRegistry();
      addTearDown(registry.dispose);
      final ShortcutRegistryEntry token = registry.addAll(const <ShortcutActivator, Intent>{
        SingleActivator(LogicalKeyboardKey.keyA): DoNothingIntent(),
      });
      token.dispose();
      expect(() {
        token.replaceAll(<ShortcutActivator, Intent>{});
      }, throwsFlutterError);
    });

    testWidgets('setting duplicate bindings asserts', (WidgetTester tester) async {
      final ShortcutRegistry registry = ShortcutRegistry();
      addTearDown(registry.dispose);
      final ShortcutRegistryEntry token = registry.addAll(const <ShortcutActivator, Intent>{
        SingleActivator(LogicalKeyboardKey.keyA): DoNothingIntent(),
      });
      expect(() {
        final ShortcutRegistryEntry token2 = registry.addAll(const <ShortcutActivator, Intent>{
          SingleActivator(LogicalKeyboardKey.keyA): ActivateIntent(),
        });
        token2.dispose();
      }, throwsAssertionError);
      token.dispose();
    });

    test('dispatches object creation in constructor', () async {
      await expectLater(
        await memoryEvents(() => ShortcutRegistry().dispose(), ShortcutRegistry),
        areCreateAndDispose,
      );
    });
  });
}

class TestCallbackRegistration extends StatefulWidget {
  const TestCallbackRegistration({
    super.key,
    required this.shortcuts,
    this.onDependencyUpdate,
    required this.child,
  });

  final Map<ShortcutActivator, Intent> shortcuts;
  final void Function(Map<ShortcutActivator, Intent> shortcuts)? onDependencyUpdate;
  final Widget child;

  @override
  State<TestCallbackRegistration> createState() => _TestCallbackRegistrationState();
}

class _TestCallbackRegistrationState extends State<TestCallbackRegistration> {
  ShortcutRegistryEntry? _registryToken;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _registryToken?.dispose();
    _registryToken = ShortcutRegistry.of(context).addAll(widget.shortcuts);
  }

  @override
  void didUpdateWidget(TestCallbackRegistration oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.shortcuts != oldWidget.shortcuts || _registryToken == null) {
      _registryToken?.dispose();
      _registryToken = ShortcutRegistry.of(context).addAll(widget.shortcuts);
    }
    widget.onDependencyUpdate?.call(ShortcutRegistry.of(context).shortcuts);
  }

  @override
  void dispose() {
    _registryToken?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

class TestAction extends CallbackAction<Intent> {
  TestAction({required super.onInvoke});
}

/// An activator that accepts down events that has [key] as the logical key.
///
/// This class is used only to tests. It is intentionally designed poorly by
/// returning null in [triggers], and checks [key] in [accepts].
class DumbLogicalActivator extends ShortcutActivator {
  const DumbLogicalActivator(this.key);

  final LogicalKeyboardKey key;

  @override
  Iterable<LogicalKeyboardKey>? get triggers => null;

  @override
  bool accepts(KeyEvent event, HardwareKeyboard state) {
    return (event is KeyDownEvent || event is KeyRepeatEvent) && event.logicalKey == key;
  }

  /// Returns a short and readable description of the key combination.
  ///
  /// Intended to be used in debug mode for logging purposes. In release mode,
  /// [debugDescribeKeys] returns an empty string.
  @override
  String debugDescribeKeys() {
    String result = '';
    assert(() {
      result = key.keyLabel;
      return true;
    }());
    return result;
  }
}

class TestIntent extends Intent {
  const TestIntent();
}

class TestIntent2 extends Intent {
  const TestIntent2();
}

class TestShortcutManager extends ShortcutManager {
  TestShortcutManager(this.keys, {super.shortcuts});

  List<LogicalKeyboardKey> keys;

  @override
  KeyEventResult handleKeypress(BuildContext context, KeyEvent event) {
    if (event is KeyDownEvent || event is KeyRepeatEvent) {
      keys.add(event.logicalKey);
    }
    return super.handleKeypress(context, event);
  }
}

Widget activatorTester(
  ShortcutActivator activator,
  ValueSetter<Intent> onInvoke, [
  ShortcutActivator? activator2,
  ValueSetter<Intent>? onInvoke2,
]) {
  final bool hasSecond = activator2 != null && onInvoke2 != null;
  return Actions(
    key: GlobalKey(),
    actions: <Type, Action<Intent>>{
      TestIntent: TestAction(
        onInvoke: (Intent intent) {
          onInvoke(intent);
          return true;
        },
      ),
      if (hasSecond)
        TestIntent2: TestAction(
          onInvoke: (Intent intent) {
            onInvoke2(intent);
            return null;
          },
        ),
    },
    child: Shortcuts(
      shortcuts: <ShortcutActivator, Intent>{
        activator: const TestIntent(),
        if (hasSecond) activator2: const TestIntent2(),
      },
      child: const Focus(autofocus: true, child: SizedBox(width: 100, height: 100)),
    ),
  );
}
