Questions asked from the app went to Claude alone. Settings › Assistant
now chooses where they open (decided 2026-10-14): Lumo, the default, by
its guest chat with the question in the address's fragment (Lumo's
`prefill`, which needs no account and reaches no server; the address for
one's own account loses the question when nobody is signed in), ChatGPT,
Claude, Mistral, Perplexity, Duck.ai, Grok, Kagi Assistant, HuggingChat,
or an address of one's own with %s. Each link fills the question in for
the person to send (Perplexity answers at once, Grok asks first); Gemini,
which has no such link, and Copilot, whose stopped working, are left out.
The field names the assistant ("Ask Lumo…"). A device that sent questions
to Claude goes on asking an assistant, now Lumo. Connecting Claude to read
studysets stays as it was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
167 lines
5.4 KiB
Dart
167 lines
5.4 KiB
Dart
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:halkyon_learn_flutter/src/core/device_settings/model/device_settings.dart';
|
|
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/assistant/model/connected_assistant.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/assistant/widget/connect_claude_screen.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/settings/widget/settings_screen.dart';
|
|
import 'package:material_ui/material_ui.dart';
|
|
|
|
import '../../support/fake_assistant_gateway.dart';
|
|
import '../../support/pump_app.dart';
|
|
import '../../support/test_dependencies.dart';
|
|
|
|
void main() {
|
|
late FakeAssistantGateway gateway;
|
|
|
|
setUp(() {
|
|
gateway = FakeAssistantGateway(
|
|
initially: [
|
|
ConnectedAssistant(
|
|
id: 1,
|
|
name: 'Claude',
|
|
host: 'claude.ai',
|
|
connectedAt: DateTime.utc(2026, 10, 9),
|
|
lastUsedAt: DateTime.utc(2026, 10, 10),
|
|
),
|
|
],
|
|
);
|
|
});
|
|
|
|
Future<void> showApp(
|
|
WidgetTester tester, {
|
|
String location = RoutePaths.connectAssistant,
|
|
bool on = true,
|
|
}) async {
|
|
tester.view.physicalSize = const Size(400, 1000);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
await pumpApp(
|
|
tester,
|
|
testDependencies(
|
|
assistantGateway: gateway,
|
|
deviceSettings: DeviceSettings(assistant: on),
|
|
),
|
|
location: location,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
/// Brings [finder] in Settings into view.
|
|
Future<void> scrollTo(WidgetTester tester, Finder finder) async {
|
|
await tester.scrollUntilVisible(
|
|
finder,
|
|
200,
|
|
scrollable: find
|
|
.descendant(
|
|
of: find.byType(SettingsScreen),
|
|
matching: find.byType(Scrollable),
|
|
)
|
|
.first,
|
|
);
|
|
await tester.ensureVisible(finder);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
testWidgets('Settings offers to connect Claude only while asking Claude is '
|
|
'on', (tester) async {
|
|
await showApp(tester, location: RoutePaths.settings, on: false);
|
|
await scrollTo(tester, find.text('Ask an AI assistant or search the web'));
|
|
expect(find.text('Connect Claude'), findsNothing);
|
|
|
|
await tester.tap(find.text('Ask an AI assistant or search the web'));
|
|
await tester.pumpAndSettle();
|
|
await scrollTo(tester, find.text('Connect Claude'));
|
|
await tester.tap(find.text('Connect Claude'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(ConnectClaudeScreen), findsOneWidget);
|
|
});
|
|
|
|
testWidgets("shows the connector's address to add in Claude, to copy", (
|
|
tester,
|
|
) async {
|
|
final copied = <Object?>[];
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
(call) async {
|
|
if (call.method == 'Clipboard.setData') {
|
|
copied.add((call.arguments as Map)['text']);
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
null,
|
|
),
|
|
);
|
|
await showApp(tester);
|
|
|
|
expect(find.text('https://learn.example.org/mcp'), findsOneWidget);
|
|
expect(find.textContaining('learn.example.org through'), findsOneWidget);
|
|
await tester.tap(find.byTooltip('Copy'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(copied, ['https://learn.example.org/mcp']);
|
|
expect(find.text('Copied'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('shows a code on asking, and a new one replaces it', (
|
|
tester,
|
|
) async {
|
|
await showApp(tester);
|
|
|
|
await tester.tap(find.text('Show a code'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('K7QX-M2P0'), findsOneWidget);
|
|
expect(find.textContaining('Valid until'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('New code'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('K7QX-M2P0'), findsNothing);
|
|
expect(find.text('K7QX-M2P1'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('lists what is connected; disconnecting asks first', (
|
|
tester,
|
|
) async {
|
|
await showApp(tester);
|
|
expect(find.text('Claude'), findsOneWidget);
|
|
expect(find.textContaining('claude.ai'), findsOneWidget);
|
|
|
|
await tester.tap(find.widgetWithText(TextButton, 'Disconnect'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Disconnect Claude?'), findsOneWidget);
|
|
await tester.tap(find.widgetWithText(FilledButton, 'Disconnect'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(gateway.connected, isEmpty);
|
|
expect(find.text('Nothing connected yet.'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('an instance without a connector says so, and shows no steps', (
|
|
tester,
|
|
) async {
|
|
gateway = FakeAssistantGateway(url: null);
|
|
await showApp(tester);
|
|
|
|
expect(find.textContaining("doesn't offer a connector"), findsOneWidget);
|
|
expect(find.text('Show a code'), findsNothing);
|
|
});
|
|
|
|
testWidgets('an instance out of reach says so, and asks again on Try '
|
|
'again', (tester) async {
|
|
gateway.unreachable = true;
|
|
await showApp(tester);
|
|
expect(find.textContaining("can't be reached"), findsOneWidget);
|
|
|
|
gateway.unreachable = false;
|
|
await tester.tap(find.text('Try again'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.textContaining("can't be reached"), findsNothing);
|
|
expect(find.text('https://learn.example.org/mcp'), findsOneWidget);
|
|
});
|
|
}
|