diff --git a/lib/src/components/badges/indicator.dart b/lib/src/components/badges/indicator.dart index 9cb37c41..bba6bb94 100644 --- a/lib/src/components/badges/indicator.dart +++ b/lib/src/components/badges/indicator.dart @@ -92,6 +92,7 @@ class ZetaIndicator extends ZetaStatelessWidget { int? value, bool? inverse, Key? key, + String? semanticLabel, }) { return ZetaIndicator( key: key ?? this.key, @@ -100,6 +101,7 @@ class ZetaIndicator extends ZetaStatelessWidget { icon: icon ?? this.icon, value: value ?? this.value, inverse: inverse ?? this.inverse, + semanticLabel: semanticLabel ?? this.semanticLabel, ); } @@ -111,18 +113,20 @@ class ZetaIndicator extends ZetaStatelessWidget { final sizePixels = _getSizePixels(size, type, context); return Semantics( - value: semanticLabel ?? value?.toString() ?? '', + label: semanticLabel, child: Container( width: sizePixels + Zeta.of(context).spacing.minimum, height: sizePixels + Zeta.of(context).spacing.minimum, - decoration: BoxDecoration( - border: Border.all( - width: ZetaBorders.medium, - color: Zeta.of(context).colors.borderSubtle, - ), - color: (inverse ? foregroundColor : Colors.transparent), - borderRadius: Zeta.of(context).radius.full, - ), + decoration: type == ZetaIndicatorType.icon + ? BoxDecoration( + border: Border.all( + width: ZetaBorders.medium, + color: Zeta.of(context).colors.borderSubtle, + ), + color: (inverse ? foregroundColor : Colors.transparent), + borderRadius: Zeta.of(context).radius.full, + ) + : null, child: Center( child: Container( width: sizePixels, @@ -155,12 +159,15 @@ class ZetaIndicator extends ZetaStatelessWidget { ); case ZetaIndicatorType.notification: return Center( - child: Text( - value.formatMaxChars(), - style: ZetaTextStyles.labelIndicator.copyWith( - color: foregroundColor, - fontSize: size == ZetaWidgetSize.large ? 12 : 11, - height: size == ZetaWidgetSize.large ? 1 : (12 / 16), + child: ExcludeSemantics( + excluding: semanticLabel != null, + child: Text( + value.formatMaxChars(), + style: ZetaTextStyles.labelIndicator.copyWith( + color: foregroundColor, + fontSize: size == ZetaWidgetSize.large ? 12 : 11, + height: size == ZetaWidgetSize.large ? 1 : (0.5 / 16), + ), ), ), ); diff --git a/lib/src/components/navigation bar/navigation_bar.dart b/lib/src/components/navigation bar/navigation_bar.dart index a328a274..490acc2c 100644 --- a/lib/src/components/navigation bar/navigation_bar.dart +++ b/lib/src/components/navigation bar/navigation_bar.dart @@ -239,7 +239,7 @@ class NavigationItem extends ZetaStatelessWidget { right: Zeta.of(context).spacing.minimum, child: DecoratedBox( decoration: BoxDecoration( - color: colors.surfacePrimary, + color: colors.surfaceDefault, borderRadius: Zeta.of(context).radius.full, ), child: item.badge?.copyWith( @@ -249,6 +249,7 @@ class NavigationItem extends ZetaStatelessWidget { ? ZetaWidgetSize.medium : null, type: ZetaIndicatorType.notification, + semanticLabel: item.badge?.semanticLabel, ), ), ); @@ -268,7 +269,6 @@ class NavigationItem extends ZetaStatelessWidget { highlightShape: BoxShape.rectangle, child: Semantics( button: true, - excludeSemantics: true, label: item.label, child: Container( padding: EdgeInsets.only( @@ -293,9 +293,11 @@ class NavigationItem extends ZetaStatelessWidget { ), SizedBox(height: Zeta.of(context).spacing.small), if (item.label != null) - Text( - item.label!, - style: Theme.of(context).textTheme.labelSmall?.copyWith(color: elementColor), + ExcludeSemantics( + child: Text( + item.label!, + style: Theme.of(context).textTheme.labelSmall?.copyWith(color: elementColor), + ), ), ], ), diff --git a/test/src/components/badge/golden/indicator_notification_with_value.png b/test/src/components/badge/golden/indicator_notification_with_value.png new file mode 100644 index 00000000..edd743bf Binary files /dev/null and b/test/src/components/badge/golden/indicator_notification_with_value.png differ diff --git a/test/src/components/badge/indicator_test.dart b/test/src/components/badge/indicator_test.dart index 43c683a8..ba73efff 100644 --- a/test/src/components/badge/indicator_test.dart +++ b/test/src/components/badge/indicator_test.dart @@ -13,7 +13,46 @@ void main() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('Accessibility Tests', () {}); + group('Accessibility Tests', () { + for (int i = 0; i < 10; i++) { + testWidgets('medium notification value $i meets accessibility standards', (tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + TestApp( + home: ZetaIndicator( + size: ZetaWidgetSize.medium, + value: i, + ), + ), + ); + + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + handle.dispose(); + }); + + testWidgets('default notification value $i meets accessibility standards', (tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + TestApp( + home: ZetaIndicator( + value: i, + ), + ), + ); + + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + handle.dispose(); + }); + } + }); group('Content Tests', () { final debugFillProperties = { 'color': 'MaterialColor(primary value: Color(0xffff9800))', @@ -170,6 +209,11 @@ void main() { 'indicator_icon_values', ); goldenTest(goldenFile, const ZetaIndicator.notification(), 'indicator_notification_default'); + goldenTest( + goldenFile, + const ZetaIndicator.notification(value: 3, size: ZetaWidgetSize.medium), + 'indicator_notification_with_value', + ); goldenTest( goldenFile, const ZetaIndicator.notification( diff --git a/test/src/components/navigation_bar/navigation_bar_test.dart b/test/src/components/navigation_bar/navigation_bar_test.dart index 11c2e082..952f2ac2 100644 --- a/test/src/components/navigation_bar/navigation_bar_test.dart +++ b/test/src/components/navigation_bar/navigation_bar_test.dart @@ -17,8 +17,13 @@ void main() { }); const items = [ - ZetaNavigationBarItem(icon: ZetaIcons.star, label: 'Label0', badge: ZetaIndicator(value: 2)), - ZetaNavigationBarItem(icon: ZetaIcons.star, label: 'Label1'), + ZetaNavigationBarItem( + icon: ZetaIcons.star, + label: 'Label0', + badge: ZetaIndicator( + value: 2, + )), + ZetaNavigationBarItem(icon: ZetaIcons.star, label: 'Label1', badge: ZetaIndicator(value: 2)), ZetaNavigationBarItem(icon: ZetaIcons.star, label: 'Label2'), ZetaNavigationBarItem(icon: ZetaIcons.star, label: 'Label3'), ]; @@ -29,6 +34,7 @@ void main() { group('Accessibility Tests', () { testWidgets('meets accessibility requirements', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( TestApp( home: ZetaNavigationBar( @@ -42,9 +48,11 @@ void main() { await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); await expectLater(tester, meetsGuideline(textContrastGuideline)); + handle.dispose(); }); testWidgets('meets accessibility requirements with action', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( TestApp( home: ZetaNavigationBar.action( @@ -58,9 +66,11 @@ void main() { await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); await expectLater(tester, meetsGuideline(textContrastGuideline)); + handle.dispose(); }); testWidgets('meets accessibility requirements with divider', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( const TestApp( home: ZetaNavigationBar.divided( @@ -74,9 +84,11 @@ void main() { await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); await expectLater(tester, meetsGuideline(textContrastGuideline)); + handle.dispose(); }); testWidgets('meets accessibility requirements with split', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( const TestApp( home: ZetaNavigationBar.split( @@ -89,21 +101,25 @@ void main() { await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); await expectLater(tester, meetsGuideline(textContrastGuideline)); + handle.dispose(); }); - testWidgets('items have semantic labels', (WidgetTester tester) async { + testWidgets('meets accessibility requirements with shrink items', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( TestApp( home: ZetaNavigationBar( items: items, + shrinkItems: true, ), ), ); - for (int i = 0; i < items.length; i++) { - final itemsFinder = find.bySemanticsLabel('Label$i'); - expect(itemsFinder, findsOneWidget); - } + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + handle.dispose(); }); });