Files
clide/test/builtin/menubar/file_actions_test.dart
T
jpmschweitzerandClaude Opus 4.8 e0fba4a3bc add Zed-style application menu bar: File / View / Help (T-48)
A custom in-window menu bar in the hat (no native menu, D-7), built from
the command registry so it stays in sync and satisfies D-6 parity.

- Menu model + hybrid resolver (menu_model.dart): a curated File/View/Help
  tree where a MenuAutoFill node sweeps in unplaced view.* commands; titles
  + keybindings come from the registry/keymap; unregistered or
  enabledWhen-false items render disabled (greyed), never hidden.
- Widgets: MenuBar row in the hat (chrome tokens), anchored MenuDropdown
  overlay (dropdown tokens), two-column MenuItemRow with inline keybinding.
- Full keyboard: Alt+mnemonic opens (hook in _RootShell._onKey), arrows
  navigate, Enter activates, Esc closes, Left/Right switch menus.
- Commands: file.openFolder / file.newWindow / file.closeWorkspace /
  help.about, registered by MenuBarExtension(services:). File logic lifted
  out of the project switcher into FileActions (one source of truth; the
  switcher now dispatches the commands). Ctrl+O / Ctrl+Shift+N are now real
  keybindings in default.yaml.
- Help → About: version/commit/date/repo from build-info + the bundled
  dependency licenses parsed from assets/licenses.yaml.

Edit/Selection menus are deferred to T-271/T-272 (need focused-surface
command routing).

Tests: resolver + controller + licenses parse (pure); menu-bar widget
(open/close/execute/disabled/Esc/arrow/Enter/Left-Right); FileActions +
Open dialog; app-level Alt+F, non-repo dialog, and closeWorkspace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:39:56 +02:00

84 lines
2.9 KiB
Dart

/// Tests for FileActions + the typed-path Open dialog (T-48). The open/close
/// paths drive real `git` via project.open, so they run as plain async tests
/// (no fake-async).
library;
import 'dart:io';
import 'package:clide/builtin/menubar/src/file_actions.dart';
import 'package:clide/extension/extension.dart';
import 'package:clide/kernel/kernel.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/kernel_fixture.dart';
void main() {
late KernelFixture f;
setUp(() async {
f = await KernelFixture.create();
// A landing tab so openPath's activateTab has a real target.
f.services.panels.contribute(TabContribution(id: 'claude.primary', slot: Slots.workspace, title: 'Claude', build: (_) => const SizedBox()));
});
tearDown(() => f.dispose());
test('openPath opens a git repo and activates the landing tab', () async {
final ok = await FileActions(f.services).openPath(Directory.current.path);
expect(ok, isTrue);
expect(f.services.project.isOpen, isTrue);
expect(f.services.panels.activeTabIn(Slots.workspace), 'claude.primary');
});
test('openPath returns false for a non-repo directory', () async {
final tmp = await Directory.systemTemp.createTemp('clide-fa-');
addTearDown(() => tmp.delete(recursive: true));
expect(await FileActions(f.services).openPath(tmp.path), isFalse);
});
test('closeWorkspace closes the active project', () async {
final fa = FileActions(f.services);
await fa.openPath(Directory.current.path);
expect(f.services.project.isOpen, isTrue);
fa.closeWorkspace();
expect(f.services.project.isOpen, isFalse);
});
Widget harness(Widget child) => Directionality(
textDirection: TextDirection.ltr,
child: ClideKernel(
services: f.services,
child: ClideTheme(
controller: f.services.theme,
child: MediaQuery(
data: const MediaQueryData(),
child: Align(alignment: Alignment.topLeft, child: child),
),
),
),
);
testWidgets('OpenFolderDialog submits the typed path via onOpen', (tester) async {
String? opened;
await tester.pumpWidget(harness(OpenFolderDialog(
onOpen: (p) async => opened = p,
onCancel: () {},
)));
await tester.enterText(find.byType(EditableText), '/some/repo');
await tester.tap(find.text('Open'));
await tester.pump();
expect(opened, '/some/repo');
});
testWidgets('OpenFolderDialog surfaces an error when onOpen throws', (tester) async {
await tester.pumpWidget(harness(OpenFolderDialog(
onOpen: (_) async => throw StateError('not a repo'),
onCancel: () {},
)));
await tester.enterText(find.byType(EditableText), '/bad');
await tester.tap(find.text('Open'));
await tester.pump();
expect(find.text('Not a git repository'), findsOneWidget);
});
}