runVantagePluginContractTests function
Runs the shared contract tests every VantagePlugin must satisfy. Call from a plugin's own test file:
void main() => runVantagePluginContractTests('MyPlugin', MyPlugin.new);
Implementation
void runVantagePluginContractTests(
String description,
VantagePlugin Function() create,
) {
group('$description — VantagePlugin contract', () {
test('id and title are non-empty', () {
final plugin = create();
expect(plugin.id, isNotEmpty, reason: 'id must be non-empty');
expect(plugin.title, isNotEmpty, reason: 'title must be non-empty');
});
test('id is stable across instances', () {
expect(create().id, create().id, reason: 'id must be stable');
});
test('quick action ids are unique and non-empty', () {
final plugin = create();
if (plugin is! QuickActionsPlugin) return;
final ids = plugin.quickActions.map((a) => a.id).toList();
expect(ids, everyElement(isNotEmpty));
expect(
ids.toSet().length,
ids.length,
reason: 'quick action ids must be unique',
);
});
testWidgets('buildPanel builds without error', (tester) async {
final plugin = create();
if (plugin is! PanelPlugin) return;
final scope = FakeVantageScope();
addTearDown(scope.dispose);
await tester.pumpWidget(
_harness(scope, (context) => plugin.buildPanel(context, scope)),
);
expect(tester.takeException(), isNull);
});
testWidgets('buildOverlay builds without error', (tester) async {
final plugin = create();
if (plugin is! OverlayPlugin) return;
final scope = FakeVantageScope();
addTearDown(scope.dispose);
await tester.pumpWidget(
_harness(scope, (context) => plugin.buildOverlay(context, scope)),
);
expect(tester.takeException(), isNull);
});
testWidgets('wrapApp returns a subtree containing the child',
(tester) async {
final plugin = create();
if (plugin is! AppWrapperPlugin) return;
final scope = FakeVantageScope();
addTearDown(scope.dispose);
const childKey = Key('vantage.contract.child');
await tester.pumpWidget(
_harness(
scope,
(context) => plugin.wrapApp(
context,
const SizedBox(key: childKey),
scope,
),
),
);
expect(
find.byKey(childKey),
findsOneWidget,
reason: 'wrapApp must include the child in its result',
);
});
test('onAttach then onDetach run cleanly against a scope', () {
final plugin = create();
// `_attachedScope` runs onAttach now and onDetach in teardown, so this test
// fails if either throws — and each runs exactly once per instance. No test
// in this suite calls onDetach itself; that would double-detach and quietly
// demand idempotency the contract never promised.
final scope = _attachedScope(plugin);
expect(
scope.isHudOpen,
isFalse,
reason: 'onAttach must not open the HUD',
);
expect(
scope.simulation,
VantageSimulationData.none,
reason: 'onAttach must not mutate the simulation behind the user',
);
});
test('EventSource.events is a broadcast stream', () async {
final plugin = create();
if (plugin is! EventSource) return;
_attachedScope(plugin);
expectVantageEventSourceIsBroadcast(plugin);
// Two concurrent listeners is the shape the host actually creates (host
// relay + a panel), and it throws on a single-subscription stream.
final first = plugin.events.listen((_) {});
final second = plugin.events.listen((_) {});
addTearDown(() async {
await first.cancel();
await second.cancel();
});
});
test('ExportContributor artifacts are bundle-safe and pre-redacted',
() async {
final plugin = create();
if (plugin is! ExportContributor) return;
_attachedScope(plugin);
final artifacts = await plugin.buildExport(
const VantageExportContext(redactor: VantageDefaultRedactor()),
);
expectVantageExportArtifactsSafe(artifacts);
});
test('ExportContributor survives an unavailable screenshot', () async {
final plugin = create();
if (plugin is! ExportContributor) return;
final scope = _attachedScope(plugin);
expect(
await scope.captureApp(),
isNull,
reason: 'the fake degrades like a target without rasterization',
);
await expectLater(
plugin.buildExport(
const VantageExportContext(redactor: VantageDefaultRedactor()),
),
completes,
);
});
});
}