// The doctor's "declare the system AI's Ollama as a host service" // suggestion and the shell's one-time update hint — both are pure // functions so every branch is pinned here. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:chain_studio/data/hub.dart'; import 'package:chain_studio/main.dart' show StudioApp, shouldShowUpdateHint; import 'package:chain_studio/pages/doctor.dart' show suggestOllamaServiceEndpoint; import 'support/fake_hub.dart'; SystemAiStatus _ai({ bool enabled = true, String provider = 'ollama', String endpoint = 'http://127.0.0.1:11434/v1', }) => SystemAiStatus( enabled: enabled, provider: provider, endpoint: endpoint, model: 'qwen2.5:14b', privacyMode: 'off', apiKeyEnv: '', ); void main() { group('suggestOllamaServiceEndpoint', () { test('suggests the base URL with the /v1 suffix stripped', () { expect( suggestOllamaServiceEndpoint(ai: _ai(), services: const []), 'http://127.0.0.1:11434', ); }); test('nothing to suggest when AI is off or another provider', () { expect( suggestOllamaServiceEndpoint(ai: _ai(enabled: false), services: const []), isNull, ); expect( suggestOllamaServiceEndpoint( ai: _ai(provider: 'openai'), services: const [], ), isNull, ); expect( suggestOllamaServiceEndpoint(ai: _ai(endpoint: ''), services: const []), isNull, ); }); test('a declared service on the same host:port silences it', () { expect( suggestOllamaServiceEndpoint( ai: _ai(), services: const [ ServiceEntry( name: 'my-llm', endpoint: 'http://127.0.0.1:11434', tags: [], ), ], ), isNull, ); }); test('a service on another port does not silence it', () { expect( suggestOllamaServiceEndpoint( ai: _ai(), services: const [ ServiceEntry( name: 'judge-ner', endpoint: 'http://127.0.0.1:8756', tags: [], ), ], ), 'http://127.0.0.1:11434', ); }); }); testWidgets('the shell hints once and dismisses persistently', ( tester, ) async { SharedPreferences.setMockInitialValues({}); final fake = installFakeHub(); fake.updateHint = const UpdateStatus( channel: 'stable', localVersion: '0.22.0', latestVersion: '0.23.0', updateAvailable: true, manifestReachable: true, ); await tester.pumpWidget( const StudioApp( initialThemeMode: ThemeModeValue.dark, initialLocale: Locale('de'), ), ); for (var i = 0; i < 8; i++) { await tester.pump(const Duration(milliseconds: 100)); } expect(find.textContaining('0.23.0'), findsOneWidget); await tester.tap(find.text('Später')); await tester.pump(const Duration(milliseconds: 100)); expect(find.textContaining('0.23.0'), findsNothing); // Dismissal is persisted per version. final prefs = await SharedPreferences.getInstance(); expect(prefs.getString('update.hint.dismissed'), '0.23.0'); // Drain shell timers (a11y-suite pattern). await tester.pumpWidget(const SizedBox.shrink()); await tester.pump(const Duration(minutes: 1)); }); group('shouldShowUpdateHint', () { test('hints once per release version', () { expect( shouldShowUpdateHint(latestVersion: '0.23.0', dismissedVersion: null), isTrue, ); expect( shouldShowUpdateHint( latestVersion: '0.23.0', dismissedVersion: '0.22.0', ), isTrue, ); expect( shouldShowUpdateHint( latestVersion: '0.23.0', dismissedVersion: '0.23.0', ), isFalse, ); expect( shouldShowUpdateHint(latestVersion: '', dismissedVersion: null), isFalse, ); }); }); }