flutter-adaptive-cards-testing

star 3

Testing patterns, utilities, and golden image workflows for the flutter_adaptive_cards_fs library (and related package test dirs). Use this before writing or modifying tests under packages/flutter_adaptive_cards_fs/test/ or packages/flutter_adaptive_cards_host_fs/test/.

freemansoft By freemansoft schedule Updated 6/13/2026

name: flutter-adaptive-cards-testing description: > Testing patterns, utilities, and golden image workflows for the flutter_adaptive_cards_fs library (and related package test dirs). Use this before writing or modifying tests under packages/flutter_adaptive_cards_fs/test/ or packages/flutter_adaptive_cards_host_fs/test/.

Flutter Adaptive Cards Testing Skill

Overview

All library tests live under:

packages/flutter_adaptive_cards_fs/test/

Tests are run from that package directory, not the monorepo root. Prefix commands with fvm per adaptive-cards-dart-flutter-fvm.

cd packages/flutter_adaptive_cards_fs
fvm flutter test                        # all tests
fvm flutter test test/golden_sample_test.dart  # specific file
fvm flutter test --tags golden          # only golden image tests
fvm flutter test --update-goldens       # regenerate golden images

Before declaring library work done, run at least:

cd packages/flutter_adaptive_cards_fs && fvm flutter test --exclude-tags=golden

Re-run targeted files when you touched related behavior (inputs, overlays, actions, goldens).

flutter_adaptive_cards_host_fs (no goldens)

Unit tests only — run from the host package directory:

cd packages/flutter_adaptive_cards_host_fs
fvm flutter test

See adaptive-cards-backend-host for handler/adapter test file paths. Core associatedInputs behavior is tested in packages/flutter_adaptive_cards_fs/test/utils/associated_inputs_test.dart.


Key-First Testing (Mandatory)

To ensure tests are resilient to UI refactoring, always locate widgets via generateWidgetKey() / generateAdaptiveWidgetKey() rather than hardcoded ValueKey strings or text/type finders.

import 'package:flutter_adaptive_cards_fs/src/utils/utils.dart';

// Extract the element map from the card body before assertions
final elementMap = map['body'][0] as Map<String, dynamic>;

// [GOOD] Derived from the same map used to build the widget
expect(find.byKey(generateWidgetKey(elementMap)), findsOneWidget);

// [GOOD] Outer StatefulWidget wrapper
expect(find.byKey(generateAdaptiveWidgetKey(elementMap)), findsOneWidget);

// [GOOD] ChoiceSet item (suffix form)
expect(
  find.byKey(generateWidgetKey(elementMap, suffix: 'Choice 1')),
  findsOneWidget,
);

// [NEVER] Hard-coded string — breaks silently if the id format changes
// find.byKey(const ValueKey('submitButton'))    ← do NOT do this

// [AVOID] Brittle and broken by text changes
// find.text('Submit')

// [AVOID] Ambiguous in large cards
// find.byType(ElevatedButton)

[!IMPORTANT] Never write find.byKey(const ValueKey('someId')) in tests. If the key format ever changes, string literals silently break whereas generateWidgetKey() calls continue to match the live implementation.


Core Test Utilities — test/utils/test_utils.dart

Import this in every test file:

import 'utils/test_utils.dart';

getTestWidgetFromPath & getTestWidgetFromMap — Primary Test Helpers

These helpers load an Adaptive Card (from a file or a Map) and return a fully-wrapped MaterialApp.

[!IMPORTANT] Mandatory Usage: Always use these helpers instead of RawAdaptiveCard.fromMap or AdaptiveCardsCanvas directly. They ensure that:

  1. ID Injection: Missing IDs are recursively injected into the JSON map.
  2. Context: Card-scoped Riverpod ProviderScope (registries, resolver, document notifier) and InheritedAdaptiveCardHandlers are provided via the helpers.
  3. UI Context: The card is wrapped in a MaterialApp, Scaffold, and RepaintBoundary.

Architecture Note: These helpers automatically wrap the card in:

  1. MaterialApp & Scaffold: Providing necessary theme and layout context.
  2. RepaintBoundary: With an optional key, used to target specific regions for golden images.
  3. InheritedAdaptiveCardHandlers: Injects mock handlers for onSubmit, onExecute, onChange, etc., if provided as arguments.
Widget getTestWidgetFromPath({
  required String path,           // relative to test/samples/
  Key? key,                       // targets the RepaintBoundary for Goldens
  // ... handlers
})

Widget getTestWidgetFromMap({
  required Map<String, dynamic> map,
  required String title,
  Key? key,
  // ... handlers
})

Widget Key Generation Patterns

All widgets use deterministic ValueKeys generated by two functions from package:flutter_adaptive_cards_fs/src/utils/utils.dart.

Widget Type Generator call Produced key
Card Wrapper generateAdaptiveWidgetKey(elementMap) ValueKey('{id}_adaptive')
Input Content generateWidgetKey(elementMap) ValueKey('{id}')
ChoiceSet Item generateWidgetKey(elementMap, suffix: 'Choice 1') ValueKey('{id}_Choice 1')
Modal Search generateWidgetKey(elementMap) Same id as input field

Canonical test pattern

import 'package:flutter_adaptive_cards_fs/src/utils/utils.dart';

// 1. Define the card map
final Map<String, dynamic> map = {
  'type': 'AdaptiveCard',
  'body': [
    {'type': 'Input.Text', 'id': 'myField', 'label': 'Name'},
  ],
};

// 2. Pump the widget
await tester.pumpWidget(getTestWidgetFromMap(map: map, title: 'Test'));
await tester.pumpAndSettle();

// 3. Extract element map — the single source of truth for keys
final fieldMap = map['body'][0] as Map<String, dynamic>;

// 4. Find widgets
expect(find.byKey(generateAdaptiveWidgetKey(fieldMap)), findsOneWidget); // wrapper
expect(find.byKey(generateWidgetKey(fieldMap)), findsOneWidget);          // input

// 5. Interact
await tester.enterText(find.byKey(generateWidgetKey(fieldMap)), 'hello');

Reference: See AdaptiveWidget-Key-Generation.md for the full key contract and automatic ID injection rules.


Golden Image Tests

Canonical Environment (Linux)

[!WARNING] Golden image pixels are platform-specific. This project organizes golden images into platform-specific subdirectories:

  • test/gold_files/linux/: CI source of truth — tests on the build server compare against these files.
  • test/gold_files/macos/: Local development baselines (committed for macOS developers).

Use getGoldenPath(filename) to resolve the path for the current platform.

Full workflow: packages/flutter_adaptive_cards_fs/test/gold_files/README.md (same rules for flutter_adaptive_charts_fs/test/gold_files/).

Standard Golden Pattern

testWidgets('My Card Golden', (tester) async {
  // 1. Fixed viewport
  RendererBinding.instance.renderViews.first.configuration =
      TestViewConfiguration.fromView(
        size: const Size(500, 700),
        view: PlatformDispatcher.instance.implicitView!,
      );

  const key = ValueKey('paint');

  // 2. Load and Pump
  await tester.pumpWidget(getTestWidgetFromPath(path: 'my_card.json', key: key));
  await tester.pumpAndSettle();

  // 3. Compare (Note: targets the key, uses dynamic platform path)
  await expectLater(
    find.byKey(key),
    matchesGoldenFile(getGoldenPath('my_card-base.png')),
  );
}, tags: ['golden']);

Creating new golden images

When adding a golden test or updating expected rendering:

  1. Generate locally (from the affected package):

    cd packages/flutter_adaptive_cards_fs   # or flutter_adaptive_charts_fs
    fvm flutter test test/golden_icon_test.dart --update-goldens --tags=golden
    

    On macOS this writes to test/gold_files/macos/.

  2. Seed Linux baselines for CI — copy each new or updated PNG from macos/ to linux/:

    cp test/gold_files/macos/v1_5_icon_demo.png test/gold_files/linux/v1_5_icon_demo.png
    

    CI runs on Linux and will fail if the matching file is missing under test/gold_files/linux/. Seeding from macOS is acceptable for new goldens; commit both copies.

  3. Commit the test, sample JSON, and both platform PNGs.

  4. If CI fails on pixel diff, replace linux/ files from the failed build’s artifact zip (see test/gold_files/README.md).

Local golden verification (macOS)

cd packages/flutter_adaptive_cards_fs
fvm flutter test --tags golden

Note: macOS and Linux pixels may differ slightly. Local --tags golden validates macOS baselines; Linux accuracy is confirmed in CI or by refreshing linux/ from CI artifacts.

Running Only Non-Golden Tests (Faster Iteration)

Local AI agents should prefer --exclude-tags=golden for routine verification (faster; avoids platform-specific golden failures when Linux baselines were not updated):

fvm flutter test --exclude-tags=golden

Reactive document tests (overlays, submit, reset)

Input values, visibility, TextBlock text, validation, ChoiceSet choices, and action isEnabled are backed by Riverpod document overlays, not mutated JSON. When testing:

  • Notifier unit tests: test/riverpod/adaptive_card_document_notifier_test.dart — use a ProviderContainer with baselineMapProvider.overrideWithValue(...), read adaptiveCardDocumentProvider.notifier, assert resolvedElementProvider(id) and overlaysById without building widgets.
  • initData overlays: test/inputs/init_data_overlay_test.dart — assert both UI and resolvedElementProvider(id) after initData or programmatic initInput.
  • Submit / reset: pump user interactions, then assert onSubmit mock received expected values, or tap Reset and assert UI reverts to baseline JSON values. See test/inherited_reference_resolver_test.dart, test/elements/is_visible_test.dart, test/inputs/action_reset_inputs_test.dart, and input tests under test/inputs/.
  • Dynamic choices: test/inputs/choice_set_overlay_test.dartloadInput, appendChoices, selection clear, dedupe, resetAllInputs clearing choice overlays.
  • Cascaded / dependent ChoiceSet: test/inputs/cascade_choice_set_test.dart, test/inputs/value_changed_action_reset_test.dartvalueChangedAction reset + applyUpdates choices; example (widgetbook sample): widgetbook/lib/dependent_choice_set_demo_page.dart (see form-inputs.md).
  • Data.Query / typeahead: test/inputs/choice_set_data_query_test.dartonChange passes DataQuery, loadInput refresh with choices.data, setDataQuerySession on resolved choices.data; associatedInputs merges sibling values into DataQuery when the action requests them. Filtered modal: test/inputs/choice_filter_test.dart, test/inputs/choice_set_test.dart — list/search titles, submit/onChange values (form-inputs.md § Filtered ChoiceSet).
  • Input validation overlays: test/inputs/input_error_overlay_test.dartsetInputError / clearInputError, edit clears overlay, host clearInputError, Input.Number; notifier group in adaptive_card_document_notifier_test.dart.
  • Action isEnabled overlays: test/actions/action_enabled_overlay_test.dart — sample test/samples/v1.5/action_is_enabled.json, Submit baseline + setActionEnabled; test/actions/show_card_enabled_overlay_test.dart — ShowCard expand button; notifier setActionsEnabled in adaptive_card_document_notifier_test.dart.
  • TextBlock text overlays: test/elements/text_block_text_overlay_test.dartsetText / clearText, host setText; notifier group in adaptive_card_document_notifier_test.dart (TextBlock text overlays).
  • ToggleVisibility: assert widgets appear/disappear after the action without relying on imperative tree walks; is_visible_test.dart includes visibility overlay surviving RawAdaptiveCard.rebuild().
  • Provider scope: use ProviderScope.containerOf(elementContext) to read adaptiveCardDocumentProvider, resolvedElementProvider(id), or resolvedActionProvider(id) when asserting document state directly.

Example notifier unit-test harness:

final container = ProviderContainer(
  overrides: [baselineMapProvider.overrideWithValue(baselineMap)],
);
addTearDown(container.dispose);
final notifier = container.read(adaptiveCardDocumentProvider.notifier);
notifier.seedInputValues({'myText': 'seeded'});
expect(container.read(resolvedElementProvider('myText'))?['value'], 'seeded');

Baseline + overlay model: docs/reactive-riverpod.md.

Widgetbook sample JSON (optional — not package tests)

When adding an interactive demo under widgetbook/ (sample program, not published package):

  1. Register new asset directories in widgetbook/pubspec.yaml (flutter: assets:) — each folder path must be listed explicitly.
  2. Add the use case in widgetbook/lib/adaptive_cards_use_cases.dart (or a dedicated *_page.dart when host callbacks are required).
  3. Run cd widgetbook && fvm dart run build_runner build after use-case changes.

For host-overlay knob demos (*_overlay_page.dart, setText / setFacts, page GlobalKey pattern), follow widgetbook-overlay-demos and docs/widgetbook-overlay-demos.md.

See widgetbook/README.md.

Coverage gaps

Per-field and per-type overlay coverage (what is tested vs missing) lives in the element-registry skill — Overlay test coverage. Use that matrix when adding a new overlay field or deciding whether a widget test is required for a specific Input.* / Action.* type.


Test Sample Files

Sample JSON cards live in test/samples/. Always add a new sample JSON when implementing a feature or fixing a bug to enable regression testing and designer validation.

  1. Create test/samples/feature_name.json.
  2. Reference via getTestWidgetFromPath(path: 'feature_name.json').
Install via CLI
npx skills add https://github.com/freemansoft/Flutter-AdaptiveCards --skill flutter-adaptive-cards-testing
Repository Details
star Stars 3
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator