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:
flemming-it 2026-06-09 09:32:13 +02:00
parent 7d220caaf3
commit 5c5ec4f990
2 changed files with 201 additions and 0 deletions

View 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));
}
}