A failed/unloaded store snapshot used to be indistinguishable from a known-empty store, so every missing capability was labelled 'not in store' the moment the hub or store endpoint was unreachable — a wrong claim. storeCapabilities is now nullable (null = unknown): missing caps then get the plain missing chip with an honest tooltip, no install offer and no not-in-store claim; the analyzer message says the store cannot be checked right now (EN+DE). Split and badge covered by new unit + widget tests. Signed-off-by: flemming-it <sf@flemming.it>
134 lines
4.6 KiB
Dart
134 lines
4.6 KiB
Dart
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']);
|
|
expect(split.unclassified, isEmpty);
|
|
});
|
|
|
|
test('null store set (state unknown) classifies nothing', () {
|
|
final split = splitMissingCaps(['text.extract'], null);
|
|
expect(split.installable, isEmpty);
|
|
expect(split.notInStore, isEmpty);
|
|
expect(split.unclassified, ['text.extract']);
|
|
});
|
|
});
|
|
|
|
Future<void> pumpBadge(
|
|
WidgetTester tester, {
|
|
required List<String> installable,
|
|
required List<String> 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<Tooltip>(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('unknown store state offers neither install nor claims', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: MissingModulesBadge(
|
|
installable: const [],
|
|
notInStore: const [],
|
|
unclassified: const ['text.extract'],
|
|
strings: const FlowEditorStrings(FlowEditorLocale.en),
|
|
onInstall: () => fail('no install offer while store unknown'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(find.text('1 module missing'), findsOneWidget);
|
|
expect(find.text('Install'), findsNothing);
|
|
expect(find.textContaining('not in store'), findsNothing);
|
|
final tooltip = tester.widget<Tooltip>(find.byType(Tooltip));
|
|
expect(tooltip.message, contains('store is not reachable'));
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
}
|