runVantagePluginContractTests function

void runVantagePluginContractTests(
  1. String description,
  2. VantagePlugin create()
)

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