import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:chain_studio_flow_editor/src/l10n.dart'; import 'package:chain_studio_flow_editor/src/widgets/missing_modules_badge.dart'; void main() { group('splitMissingCaps', () { test('separates store-resolvable from not-in-store capabilities', () { final split = splitMissingCaps( ['text.extract', 'example-provider/tool.summarize'], {'text.extract', 'debug.echo'}, ); expect(split.installable, ['text.extract']); expect(split.notInStore, ['example-provider/tool.summarize']); }); test('matches on the bare name when the flow pins a version', () { final split = splitMissingCaps(['text.extract@^0'], {'text.extract'}); expect(split.installable, ['text.extract@^0']); expect(split.notInStore, isEmpty); }); test('empty store set classifies everything as not-in-store', () { final split = splitMissingCaps(['text.extract'], {}); expect(split.installable, isEmpty); expect(split.notInStore, ['text.extract']); }); }); Future pumpBadge( WidgetTester tester, { required List installable, required List notInStore, VoidCallback? onInstall, }) { return tester.pumpWidget( MaterialApp( home: Scaffold( body: MissingModulesBadge( installable: installable, notInStore: notInStore, strings: const FlowEditorStrings(FlowEditorLocale.en), onInstall: onInstall, ), ), ), ); } group('MissingModulesBadge', () { testWidgets('store-resolvable capability offers the install action', ( tester, ) async { var installed = false; await pumpBadge( tester, installable: ['text.extract'], notInStore: [], onInstall: () => installed = true, ); expect(find.text('1 module missing'), findsOneWidget); expect(find.text('Install'), findsOneWidget); expect(find.textContaining('not in store'), findsNothing); await tester.tap(find.text('Install')); expect(installed, isTrue); }); testWidgets( 'not-in-store capability shows the classified state, no install', (tester) async { await pumpBadge( tester, installable: [], notInStore: ['example-provider/tool.summarize'], onInstall: () => fail('no install action for not-in-store caps'), ); expect(find.text('not in store'), findsOneWidget); expect(find.text('Install'), findsNothing); // The recovery paths are explained in place, before any click. final tooltip = tester.widget(find.byType(Tooltip)); expect(tooltip.message, contains('example-provider/tool.summarize')); expect(tooltip.message, contains('chain install --link')); expect(tooltip.message, contains('Settings → Stores')); expect(tooltip.message, contains('integration')); }, ); testWidgets('mixed state renders both chips, install covers store caps', ( tester, ) async { await pumpBadge( tester, installable: ['text.extract'], notInStore: ['example-provider/tool.summarize'], onInstall: () {}, ); expect(find.text('1 module missing'), findsOneWidget); expect(find.text('Install'), findsOneWidget); expect(find.text('not in store'), findsOneWidget); }); }); }