| name | 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
fvm flutter test test/golden_sample_test.dart
fvm flutter test --tags golden
fvm flutter test --update-goldens
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:
- ID Injection: Missing IDs are recursively injected into the JSON map.
- Context: Card-scoped Riverpod
ProviderScope (registries, resolver, document notifier) and InheritedAdaptiveCardHandlers are provided via the helpers.
- UI Context: The card is wrapped in a
MaterialApp, Scaffold, and RepaintBoundary.
Architecture Note: These helpers automatically wrap the card in:
- MaterialApp & Scaffold: Providing necessary theme and layout context.
- RepaintBoundary: With an optional
key, used to target specific regions for golden images.
- 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:
-
Generate locally (from the affected package):
cd packages/flutter_adaptive_cards_fs
fvm flutter test test/golden_icon_test.dart --update-goldens --tags=golden
On macOS this writes to test/gold_files/macos/.
-
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.
-
Commit the test, sample JSON, and both platform PNGs.
-
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 / initInput overlays: per input type —
test/inputs/text_overlay_test.dart, number_overlay_test.dart, date_overlay_test.dart, time_overlay_test.dart, toggle_overlay_test.dart, choice_set_overlay_test.dart; host compose in test/adaptive_card_overlay_test.dart.
- 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/visibility_overlay_test.dart, test/inputs/action_reset_inputs_test.dart, and input tests under test/inputs/.
- Dynamic choices:
test/inputs/choice_set_overlay_test.dart — loadInput, 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.dart — valueChangedAction 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.dart — onChange 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/text_overlay_test.dart, test/inputs/number_overlay_test.dart, test/inputs/rating_input_overlay_test.dart; notifier group in adaptive_card_document_notifier_test.dart.
- Action overlays:
test/actions/submit_overlay_test.dart, show_card_overlay_test.dart, popover_overlay_test.dart, execute_overlay_test.dart, open_url_overlay_test.dart, toggle_visibility_overlay_test.dart; sample test/samples/v1.5/action_is_enabled.json.
- Element overlays:
test/elements/text_block_overlay_test.dart, badge_overlay_test.dart, image_overlay_test.dart, media_overlay_test.dart, rating_overlay_test.dart, rich_text_block_inlines_overlay_test.dart; test/containers/fact_set_overlay_test.dart.
- Chart overlays:
packages/flutter_adaptive_charts_fs/test/charts/chart_overlay_test.dart.
- ToggleVisibility:
test/actions/toggle_visibility_overlay_test.dart; visibility notifier APIs in test/elements/visibility_overlay_test.dart (includes rebuild survival).
- 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):
- Register new asset directories in
widgetbook/pubspec.yaml (flutter: assets:) — each folder path must be listed explicitly.
- Add the use case in
widgetbook/lib/adaptive_cards_use_cases.dart (or a dedicated *_page.dart when host callbacks are required).
- 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 adaptive-cards-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.
- Create
test/samples/feature_name.json.
- Reference via
getTestWidgetFromPath(path: 'feature_name.json').
Line coverage gate
CI enforces a per-package line-coverage floor (ratchet). Coverage is measured by a
golden-excluded pass — the same command you run locally — so CI and local never diverge:
cd packages/flutter_adaptive_cards_fs
fvm flutter test --coverage --exclude-tags=golden
fvm dart run tool/coverage/check_coverage.dart
fvm dart run tool/coverage/check_coverage.dart --self-test
Floors live in tool/coverage_floors.yaml. Raise a floor (own commit) after adding
tests that lift coverage; never lower one to make a red build pass — a drop means a
missing test. Full details: docs/testing-coverage.md.