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 whereasgenerateWidgetKey()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.fromMaporAdaptiveCardsCanvasdirectly. They ensure that:
- ID Injection: Missing IDs are recursively injected into the JSON map.
- Context: Card-scoped Riverpod
ProviderScope(registries, resolver, document notifier) andInheritedAdaptiveCardHandlersare provided via the helpers.- UI Context: The card is wrapped in a
MaterialApp,Scaffold, andRepaintBoundary.
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 # or flutter_adaptive_charts_fs fvm flutter test test/golden_icon_test.dart --update-goldens --tags=goldenOn macOS this writes to
test/gold_files/macos/.Seed Linux baselines for CI — copy each new or updated PNG from
macos/tolinux/:cp test/gold_files/macos/v1_5_icon_demo.png test/gold_files/linux/v1_5_icon_demo.pngCI 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 (seetest/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 goldenvalidates macOS baselines; Linux accuracy is confirmed in CI or by refreshinglinux/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 aProviderContainerwithbaselineMapProvider.overrideWithValue(...), readadaptiveCardDocumentProvider.notifier, assertresolvedElementProvider(id)andoverlaysByIdwithout building widgets. - initData overlays:
test/inputs/init_data_overlay_test.dart— assert both UI andresolvedElementProvider(id)afterinitDataor programmaticinitInput. - Submit / reset: pump user interactions, then assert
onSubmitmock received expected values, or tap Reset and assert UI reverts to baseline JSON values. Seetest/inherited_reference_resolver_test.dart,test/elements/is_visible_test.dart,test/inputs/action_reset_inputs_test.dart, and input tests undertest/inputs/. - Dynamic choices:
test/inputs/choice_set_overlay_test.dart—loadInput,appendChoices, selection clear, dedupe,resetAllInputsclearing choice overlays. - Cascaded / dependent ChoiceSet:
test/inputs/cascade_choice_set_test.dart,test/inputs/value_changed_action_reset_test.dart—valueChangedActionreset +applyUpdateschoices; 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—onChangepassesDataQuery,loadInputrefresh withchoices.data,setDataQuerySessionon resolvedchoices.data;associatedInputsmerges sibling values intoDataQuerywhen the action requests them. Filtered modal:test/inputs/choice_filter_test.dart,test/inputs/choice_set_test.dart— list/search titles, submit/onChangevalues (form-inputs.md § Filtered ChoiceSet). - Input validation overlays:
test/inputs/input_error_overlay_test.dart—setInputError/clearInputError, edit clears overlay, hostclearInputError, Input.Number; notifier group inadaptive_card_document_notifier_test.dart. - Action
isEnabledoverlays:test/actions/action_enabled_overlay_test.dart— sampletest/samples/v1.5/action_is_enabled.json, Submit baseline +setActionEnabled;test/actions/show_card_enabled_overlay_test.dart— ShowCard expand button; notifiersetActionsEnabledinadaptive_card_document_notifier_test.dart. - TextBlock
textoverlays:test/elements/text_block_text_overlay_test.dart—setText/clearText, hostsetText; notifier group inadaptive_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.dartincludes visibility overlay survivingRawAdaptiveCard.rebuild(). - Provider scope: use
ProviderScope.containerOf(elementContext)to readadaptiveCardDocumentProvider,resolvedElementProvider(id), orresolvedActionProvider(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.dartwhen host callbacks are required). - Run
cd widgetbook && fvm dart run build_runner buildafter 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.
- Create
test/samples/feature_name.json. - Reference via
getTestWidgetFromPath(path: 'feature_name.json').