Raise the declared minimums in pubspec.yaml to what our deps already require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist 0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is the binding floor. Pin the exact build toolchain in .fvmrc (Flutter 3.44.1). Moving to the Dart 3.9 language level switches `dart format` to the new "tall" style and enables two new lints. This commit is the resulting mechanical churn, isolated from any behaviour change: - whole-tree `dart format` reformat (tall style) - `dart fix` for unnecessary_underscores + use_null_aware_elements No runtime behaviour change; `make test` green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
112 lines
3.8 KiB
Dart
112 lines
3.8 KiB
Dart
/// Widget tests for the keyboard-operable [ClideTappable] (T-100).
|
|
///
|
|
/// The pre-T-100 widget was mouse-only — these tests assert it now
|
|
/// accepts focus, that Enter / Space invoke onTap via the
|
|
/// [ActivateIntent] action, and that the focus ring appears when
|
|
/// the widget has focus.
|
|
library;
|
|
|
|
import 'package:clide/widgets/widgets.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../../helpers/kernel_fixture.dart';
|
|
import '../../helpers/widget_harness.dart';
|
|
|
|
void main() {
|
|
group('ClideTappable — keyboard', () {
|
|
late KernelFixture f;
|
|
setUp(() async => f = await KernelFixture.create());
|
|
tearDown(() async => f.dispose());
|
|
|
|
testWidgets('accepts Tab focus when onTap is provided', (tester) async {
|
|
var tapped = 0;
|
|
final node = FocusNode();
|
|
addTearDown(node.dispose);
|
|
await tester.pumpWidget(
|
|
harness(
|
|
f,
|
|
Center(
|
|
child: ClideTappable(focusNode: node, onTap: () => tapped++, builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24)),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(node.hasFocus, isFalse);
|
|
node.requestFocus();
|
|
await tester.pump();
|
|
expect(node.hasFocus, isTrue);
|
|
// Tap count unchanged — focus alone doesn't invoke.
|
|
expect(tapped, 0);
|
|
});
|
|
|
|
testWidgets('Enter on a focused tappable invokes onTap via ActivateIntent', (tester) async {
|
|
var tapped = 0;
|
|
final node = FocusNode();
|
|
addTearDown(node.dispose);
|
|
await tester.pumpWidget(
|
|
harness(
|
|
f,
|
|
Center(
|
|
child: ClideTappable(
|
|
focusNode: node,
|
|
autofocus: true,
|
|
onTap: () => tapped++,
|
|
builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump();
|
|
expect(node.hasFocus, isTrue);
|
|
// Dispatch ActivateIntent directly against the focused context
|
|
// — mirrors what KeymapService.resolveEvent → Actions.maybeInvoke
|
|
// would do on Enter / Space.
|
|
Actions.invoke(node.context!, const ActivateIntent());
|
|
await tester.pump();
|
|
expect(tapped, 1);
|
|
});
|
|
|
|
testWidgets('disabled tappable (onTap == null) cannot receive focus', (tester) async {
|
|
final node = FocusNode();
|
|
addTearDown(node.dispose);
|
|
await tester.pumpWidget(
|
|
harness(
|
|
f,
|
|
Center(
|
|
child: ClideTappable(focusNode: node, onTap: null, builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24)),
|
|
),
|
|
),
|
|
);
|
|
node.requestFocus();
|
|
await tester.pump();
|
|
expect(node.hasFocus, isFalse, reason: 'canRequestFocus is false when onTap is null');
|
|
});
|
|
|
|
testWidgets('focus ring appears when focused, gone when unfocused', (tester) async {
|
|
final node = FocusNode();
|
|
addTearDown(node.dispose);
|
|
await tester.pumpWidget(
|
|
harness(
|
|
f,
|
|
Center(
|
|
child: ClideTappable(focusNode: node, onTap: () {}, builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24)),
|
|
),
|
|
),
|
|
);
|
|
|
|
Iterable<Color> ringColors() => tester
|
|
.widgetList<DecoratedBox>(find.descendant(of: find.byType(ClideTappable), matching: find.byType(DecoratedBox)))
|
|
.map((d) => ((d.decoration as BoxDecoration).border?.top.color ?? const Color(0x00000000)));
|
|
|
|
// Unfocused: no DecoratedBox descendant carries a non-transparent border.
|
|
expect(ringColors().any((c) => c.a > 0), isFalse);
|
|
|
|
node.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(node.hasFocus, isTrue);
|
|
expect(ringColors().any((c) => c.a > 0), isTrue, reason: 'focus ring border should become opaque on focus');
|
|
});
|
|
});
|
|
}
|