feat: honest install badge — classify store resolvability before the click
Split each flow's missing capabilities into store-resolvable ones (amber chip + Install link, which now installs only those) and a quiet 'not in store' chip whose tooltip explains the three recovery paths in place: local install via chain install --link, adding the providing store, or configuring the MCP/n8n integration. The analyzer's not-in-store message names the same three paths (EN+DE). Previously the list's Install action covered every missing capability and could end in the hub's 'no store entry' error. Also replace a private-looking capability example name in a doc comment and test with a neutral placeholder. (0.25.0) Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
7d6a575cae
commit
ab97e5e834
8 changed files with 340 additions and 105 deletions
|
|
@ -135,13 +135,20 @@ steps:
|
|||
name: x
|
||||
steps:
|
||||
- id: c
|
||||
use: htw.private/secret@^0.1
|
||||
use: acme.internal/secret@^0.1
|
||||
'''));
|
||||
expect(r.issues, hasLength(1));
|
||||
final fixes = a.fixesFor(r.issues.first);
|
||||
expect(fixes, hasLength(1));
|
||||
expect(fixes.first, isA<AddModuleSourceFix>());
|
||||
expect(r.issues.first.message, contains('Not in the store'));
|
||||
// The message must explain all three recovery paths in place:
|
||||
// local install, adding a store, configuring an integration.
|
||||
expect(
|
||||
r.issues.first.message,
|
||||
contains('No configured store can install it'),
|
||||
);
|
||||
expect(r.issues.first.message, contains('chain install --link'));
|
||||
expect(r.issues.first.message, contains('integration'));
|
||||
});
|
||||
|
||||
test('did-you-mean wins over install/add-source when a near miss exists',
|
||||
|
|
|
|||
103
test/missing_modules_badge_test.dart
Normal file
103
test/missing_modules_badge_test.dart
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
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<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('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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue