diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 63ad0d13..8c72a8c7 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,7 +7,7 @@ import Foundation import path_provider_foundation import shared_preferences_foundation -import sqflite +import sqflite_darwin import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { diff --git a/lib/src/components/banner/banner.dart b/lib/src/components/banner/banner.dart index 3bb738ab..ae7e5ebd 100644 --- a/lib/src/components/banner/banner.dart +++ b/lib/src/components/banner/banner.dart @@ -101,7 +101,14 @@ class ZetaBanner extends MaterialBanner { size: Zeta.of(context).spacing.xl_2, ), ), - Flexible(child: Text(title)), + Flexible( + child: Text( + title, + style: ZetaTextStyles.labelLarge.copyWith( + color: Zeta.of(context).colors.textInverse, + ), + ), + ), ], ), ), diff --git a/test/src/components/banner/banner_test.dart b/test/src/components/banner/banner_test.dart new file mode 100644 index 00000000..c733a75f --- /dev/null +++ b/test/src/components/banner/banner_test.dart @@ -0,0 +1,359 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../../test_utils/test_app.dart'; +import '../../../test_utils/tolerant_comparator.dart'; +import '../../../test_utils/utils.dart'; + +ZetaColorSwatch _backgroundColorFromType(BuildContext context, ZetaBannerStatus type) { + final zeta = Zeta.of(context); + + switch (type) { + case ZetaBannerStatus.primary: + return zeta.colors.primary; + case ZetaBannerStatus.positive: + return zeta.colors.surfacePositive; + case ZetaBannerStatus.warning: + return zeta.colors.orange; + case ZetaBannerStatus.negative: + return zeta.colors.surfaceNegative; + } +} + +void main() { + const goldenFile = GoldenFiles(component: 'banner'); + + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('ZetaBanner Accessibility Tests', () { + for (final type in ZetaBannerStatus.values) { + testWidgets('meets contrast ratio guideline for $type', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + leadingIcon: Icons.info, + trailing: const ZetaIconButton(icon: Icons.close), + type: type, + ); + }, + ), + ), + ); + + await expectLater(tester, meetsGuideline(textContrastGuideline)); + }); + } + + testWidgets('semantic label works correctly', (WidgetTester tester) async { + String semanticLabelText = 'Banner Title'; + StateSetter? setState; + + await tester.pumpWidget( + StatefulBuilder( + builder: (context, setState2) { + setState = setState2; + return TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + semanticLabel: semanticLabelText, + ); + }, + ), + ); + }, + ), + ); + + final Semantics titleSematicLabel = tester.widgetList(find.byType(Semantics)).last; + expect(titleSematicLabel.properties.label, equals('Banner Title')); + + setState?.call(() => semanticLabelText = ''); + await tester.pumpAndSettle(); + + final Semantics titleSematicLabel2 = tester.widgetList(find.byType(Semantics)).last; + expect(titleSematicLabel2.properties.label, equals('')); + }); + + testWidgets('uses title for sematic label if nessaccary', (WidgetTester tester) async { + String titleText = 'Banner Title'; + StateSetter? setState; + + await tester.pumpWidget( + StatefulBuilder( + builder: (context, setState2) { + setState = setState2; + return TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: titleText, + ); + }, + ), + ); + }, + ), + ); + + final Semantics titleSematicLabel = tester.widgetList(find.byType(Semantics)).last; + expect(titleSematicLabel.properties.label, equals('Banner Title')); + + setState?.call(() => titleText = ''); + await tester.pumpAndSettle(); + + final Semantics titleSematicLabel2 = tester.widgetList(find.byType(Semantics)).last; + expect(titleSematicLabel2.properties.label, equals('')); + }); + }); + + group('ZetaBanner Content Tests', () { + testWidgets('ZetaBanner title is correct', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + ); + }, + ), + ), + ); + final Finder textFinder = find.text('Banner Title'); + expect(textFinder, findsOneWidget); + }); + + testWidgets('ZetaBanner leading icon is correct', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + leadingIcon: Icons.info, + ); + }, + ), + ), + ); + final Finder iconFinder = find.byIcon(Icons.info); + expect(iconFinder, findsOneWidget); + + final Icon iconWidget = tester.widget(iconFinder); + expect(iconWidget.icon, equals(Icons.info)); + }); + + testWidgets('trailing widget is correct', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + trailing: const ZetaIconButton(icon: ZetaIcons.close), + ); + }, + ), + ), + ); + + final Finder iconButtonFinder = find.byType(ZetaIconButton); + expect(iconButtonFinder, findsOneWidget); + + final ZetaIconButton button = tester.widget(iconButtonFinder); + expect(button.icon, equals(ZetaIcons.close)); + }); + + testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { + final diagnostics = DiagnosticPropertiesBuilder(); + const ZetaAccordion( + title: 'Title', + ).debugFillProperties(diagnostics); + + expect(diagnostics.finder('title'), '"Title"'); + expect(diagnostics.finder('rounded'), 'null'); + expect(diagnostics.finder('contained'), 'false'); + expect(diagnostics.finder('isOpen'), 'false'); + }); + }); + + group('ZetaBanner Dimension Tests', () { + testWidgets('icon is the correct size', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + leadingIcon: ZetaIcons.info, + ); + }, + ), + ), + ); + final Finder iconFinder = find.byIcon(ZetaIcons.info); + + final Icon iconWidget = tester.widget(iconFinder); + + expect(iconWidget.size, Zeta.of(tester.element(iconFinder)).spacing.xl_2); + }); + + testWidgets('icon padding is correct', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + leadingIcon: ZetaIcons.info, + ); + }, + ), + ), + ); + final Finder paddingFinder = find.widgetWithIcon(Padding, ZetaIcons.info); + + final Padding paddingWidget = tester.firstWidget(paddingFinder); + + expect(paddingWidget.padding, equals(const EdgeInsets.only(right: 8))); + }); + + testWidgets('banner padding is correct', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + ); + }, + ), + ), + ); + final Finder paddingFinder = find.byType(Padding); + + final Padding paddingWidget = tester.widgetList(paddingFinder).elementAt(1); + + expect(paddingWidget.padding, equals(const EdgeInsetsDirectional.only(start: 16, top: 2))); + }); + }); + + group('ZetaBanner Styling Tests', () { + for (final type in ZetaBannerStatus.values) { + testWidgets('title styles are correct for $type', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + type: type, + ); + }, + ), + ), + ); + final Finder textFinder = find.text('Banner Title'); + final Text textWidget = tester.widget(find.byType(Text)); + expect( + textWidget.style, + equals( + ZetaTextStyles.labelLarge.copyWith( + color: Zeta.of(tester.element(textFinder)).colors.textInverse, + ), + ), + ); + }); + + testWidgets('icon color is correct for $type', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + leadingIcon: Icons.info, + type: type, + ); + }, + ), + ), + ); + + final Finder iconFinder = find.byIcon(Icons.info); + + final Icon iconWidget = tester.widget(iconFinder); + expect(iconWidget.color, _backgroundColorFromType(tester.element(iconFinder), type).onColor); + }); + + testWidgets('background colors are correct for $type', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + type: type, + ); + }, + ), + ), + ); + final Finder finder = find.byType(ZetaBanner); + final ZetaBanner widget = tester.firstWidget(finder); + + expect(widget.backgroundColor, equals(_backgroundColorFromType(tester.element(finder), type))); + }); + } + }); + + group('ZetaBanner Interaction Tests', () {}); + + group('ZetaBanner Golden Tests', () { + for (final type in ZetaBannerStatus.values) { + testWidgets('ZetaBanner ${type.toString().split('.').last} golden', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + leadingIcon: Icons.info, + trailing: const ZetaIcon(Icons.chevron_right), + type: type, + ); + }, + ), + ), + ); + + await expectLater( + find.byType(ZetaBanner), + matchesGoldenFile(goldenFile.getFileUri('banner_${type.toString().split('.').last}')), + ); + }); + } + }); +} diff --git a/test/src/components/banner/golden/banner_negative.png b/test/src/components/banner/golden/banner_negative.png new file mode 100644 index 00000000..31ff7fd7 Binary files /dev/null and b/test/src/components/banner/golden/banner_negative.png differ diff --git a/test/src/components/banner/golden/banner_positive.png b/test/src/components/banner/golden/banner_positive.png new file mode 100644 index 00000000..b96308fb Binary files /dev/null and b/test/src/components/banner/golden/banner_positive.png differ diff --git a/test/src/components/banner/golden/banner_primary.png b/test/src/components/banner/golden/banner_primary.png new file mode 100644 index 00000000..c1ff44d8 Binary files /dev/null and b/test/src/components/banner/golden/banner_primary.png differ diff --git a/test/src/components/banner/golden/banner_warning.png b/test/src/components/banner/golden/banner_warning.png new file mode 100644 index 00000000..18786b14 Binary files /dev/null and b/test/src/components/banner/golden/banner_warning.png differ diff --git a/test/test_utils/utils.dart b/test/test_utils/utils.dart index e8e7acc2..1bfb59f1 100644 --- a/test/test_utils/utils.dart +++ b/test/test_utils/utils.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart'; extension Util on DiagnosticPropertiesBuilder { @@ -27,3 +29,7 @@ class GoldenFiles { .replace(scheme: 'file'); } } + +BuildContext getBuildContext(WidgetTester tester, Type type) { + return tester.element(find.byType(type)); +} diff --git a/test/testing_conventions.mdx b/test/testing_conventions.mdx new file mode 100644 index 00000000..4146566c --- /dev/null +++ b/test/testing_conventions.mdx @@ -0,0 +1,9 @@ +## Groups + +- Accessibility Tests +- Content Tests +- Dimensions Tests +- Styling Tests +- Interaction Tests +- Golden Tests +- Performance Tests