test(editor): cover quick-fix exposure + driver approval contract
Two new test files:
- fix_dialog_test.dart — surface tests for the controller API
the new Fix dialog reads: fixesFor() returns the analyzer-
attached list, analyzerErrorCount tracks errors, and the
English AnalyzerStrings produce the labels the dialog
renders ('Install <cap>', 'Add source for <cap>…', etc).
- inline_approval_test.dart — covers the FlowRunDriver
approval hooks added in 0.21.0. Validates the default
stubs throw UnsupportedError (so legacy hosts surface in
CI rather than at runtime) and that a fake driver
implementation can drive the happy + reject paths.
44 editor tests green.
Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
7d220caaf3
commit
5c5ec4f990
2 changed files with 201 additions and 0 deletions
67
test/fix_dialog_test.dart
Normal file
67
test/fix_dialog_test.dart
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Surface-level tests for the diagnostic-strip Fix dialog
|
||||||
|
// support. The dialog itself is a private widget; this file
|
||||||
|
// exercises the controller-side API the dialog reads (issues,
|
||||||
|
// fixesFor, analyzerErrorCount, AnalyzerStrings) so a future
|
||||||
|
// regression on the data flow surfaces in CI.
|
||||||
|
|
||||||
|
import 'package:fai_studio_flow_editor/src/flow_yaml_controller.dart';
|
||||||
|
import 'package:fai_studio_flow_editor/src/l10n.dart';
|
||||||
|
import 'package:fai_studio_flow_editor/src/quick_fix.dart';
|
||||||
|
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('FlowYamlCodeController quick-fix exposure', () {
|
||||||
|
test('fixesFor returns the analyzer-attached list', () async {
|
||||||
|
final c = FlowYamlCodeController(
|
||||||
|
availableCapabilities: () => const <String>['debug.echo'],
|
||||||
|
storeCapabilities: () => const <String>['text.classify'],
|
||||||
|
);
|
||||||
|
addTearDown(c.dispose);
|
||||||
|
c.fullText = '''
|
||||||
|
name: hello
|
||||||
|
steps:
|
||||||
|
- id: c
|
||||||
|
use: text.classify@^1
|
||||||
|
''';
|
||||||
|
// Wait for the analyzer's debounce.
|
||||||
|
await Future.delayed(const Duration(milliseconds: 700));
|
||||||
|
final issues = c.analysisResult.issues;
|
||||||
|
expect(
|
||||||
|
issues,
|
||||||
|
isNotEmpty,
|
||||||
|
reason: 'analyzer should flag the unknown capability',
|
||||||
|
);
|
||||||
|
final fixes = c.fixesFor(issues.first);
|
||||||
|
expect(fixes, hasLength(1));
|
||||||
|
expect(fixes.first, isA<InstallCapabilityFix>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('analyzerErrorCount tracks error severity', () async {
|
||||||
|
final c = FlowYamlCodeController(
|
||||||
|
availableCapabilities: () => const <String>['debug.echo'],
|
||||||
|
);
|
||||||
|
addTearDown(c.dispose);
|
||||||
|
expect(c.analysisResult.issues, isEmpty);
|
||||||
|
c.fullText = '''
|
||||||
|
steps:
|
||||||
|
- id: x
|
||||||
|
use: nope@^1
|
||||||
|
''';
|
||||||
|
await Future.delayed(const Duration(milliseconds: 700));
|
||||||
|
final errors = c.analysisResult.issues
|
||||||
|
.where((i) => i.type == IssueType.error)
|
||||||
|
.length;
|
||||||
|
expect(errors, greaterThan(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AnalyzerStrings.english produces the expected fix labels', () {
|
||||||
|
final strings = AnalyzerStrings.english;
|
||||||
|
expect(strings.fixInstallCap('text.echo'), 'Install text.echo');
|
||||||
|
expect(strings.fixAddSource('text.echo'), 'Add source for text.echo…');
|
||||||
|
expect(strings.fixChangeTo('bytes'), 'Change to "bytes"');
|
||||||
|
expect(strings.fixUseInstead('text.echo@^0.1'),
|
||||||
|
'Use "text.echo@^0.1"');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
134
test/inline_approval_test.dart
Normal file
134
test/inline_approval_test.dart
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
// Surface-level tests for the FlowRunDriver approval hooks
|
||||||
|
// added in editor 0.21.0. The card itself is a private widget;
|
||||||
|
// these tests cover the driver-side contract that the card
|
||||||
|
// depends on so that legacy / mis-implementing hosts surface
|
||||||
|
// in CI rather than at runtime.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:fai_studio_flow_editor/src/run_driver.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('FlowRunDriver default approval implementation', () {
|
||||||
|
test('pendingApprovalIdForStep returns null when not overridden',
|
||||||
|
() async {
|
||||||
|
final d = _NullDriver();
|
||||||
|
final id = await d.pendingApprovalIdForStep(
|
||||||
|
flowName: 'f',
|
||||||
|
stepId: 's',
|
||||||
|
);
|
||||||
|
expect(id, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('approveApproval throws UnsupportedError by default',
|
||||||
|
() async {
|
||||||
|
final d = _NullDriver();
|
||||||
|
await expectLater(
|
||||||
|
d.approveApproval(approvalId: 'x', reviewer: 'r'),
|
||||||
|
throwsA(isA<UnsupportedError>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rejectApproval throws UnsupportedError by default',
|
||||||
|
() async {
|
||||||
|
final d = _NullDriver();
|
||||||
|
await expectLater(
|
||||||
|
d.rejectApproval(approvalId: 'x', reviewer: 'r', reason: 'y'),
|
||||||
|
throwsA(isA<UnsupportedError>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('FlowRunDriver fake host implementation', () {
|
||||||
|
test('full happy path: lookup → approve → state observable',
|
||||||
|
() async {
|
||||||
|
final d = _FakeApprovalDriver();
|
||||||
|
d.pending['flowA|stepX'] = 'a-123';
|
||||||
|
|
||||||
|
final id = await d.pendingApprovalIdForStep(
|
||||||
|
flowName: 'flowA',
|
||||||
|
stepId: 'stepX',
|
||||||
|
);
|
||||||
|
expect(id, 'a-123');
|
||||||
|
|
||||||
|
await d.approveApproval(approvalId: 'a-123', reviewer: 'alice');
|
||||||
|
expect(d.approvals, hasLength(1));
|
||||||
|
expect(d.approvals.first, ('a-123', 'alice'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reject path carries reason through', () async {
|
||||||
|
final d = _FakeApprovalDriver();
|
||||||
|
d.pending['flowA|stepX'] = 'a-123';
|
||||||
|
|
||||||
|
await d.rejectApproval(
|
||||||
|
approvalId: 'a-123',
|
||||||
|
reviewer: 'alice',
|
||||||
|
reason: 'missing signature',
|
||||||
|
);
|
||||||
|
expect(d.rejections, hasLength(1));
|
||||||
|
expect(d.rejections.first.$3, 'missing signature');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimal driver that overrides nothing. Used to verify the
|
||||||
|
/// default-throw stubs from `run_driver.dart`.
|
||||||
|
class _NullDriver extends FlowRunDriver {
|
||||||
|
@override
|
||||||
|
Future<Map<String, FlowOutputValue>> runFlow({
|
||||||
|
required String flowName,
|
||||||
|
required Map<String, String> textInputs,
|
||||||
|
required Map<String, Uint8List> fileInputs,
|
||||||
|
required Map<String, String> fileMimes,
|
||||||
|
}) async =>
|
||||||
|
const {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<FlowRunEvent> events() => const Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Studio-shaped driver mock — implements every approval hook
|
||||||
|
/// + tracks side effects so tests can assert on them.
|
||||||
|
class _FakeApprovalDriver extends FlowRunDriver {
|
||||||
|
final Map<String, String> pending = {};
|
||||||
|
final List<(String, String)> approvals = [];
|
||||||
|
final List<(String, String, String)> rejections = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, FlowOutputValue>> runFlow({
|
||||||
|
required String flowName,
|
||||||
|
required Map<String, String> textInputs,
|
||||||
|
required Map<String, Uint8List> fileInputs,
|
||||||
|
required Map<String, String> fileMimes,
|
||||||
|
}) async =>
|
||||||
|
const {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<FlowRunEvent> events() => const Stream.empty();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> pendingApprovalIdForStep({
|
||||||
|
required String flowName,
|
||||||
|
required String stepId,
|
||||||
|
}) async =>
|
||||||
|
pending['$flowName|$stepId'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> approveApproval({
|
||||||
|
required String approvalId,
|
||||||
|
required String reviewer,
|
||||||
|
}) async {
|
||||||
|
approvals.add((approvalId, reviewer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rejectApproval({
|
||||||
|
required String approvalId,
|
||||||
|
required String reviewer,
|
||||||
|
required String reason,
|
||||||
|
}) async {
|
||||||
|
rejections.add((approvalId, reviewer, reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue