From 60a137f17dbeb989cf2a9f0b0dc3ee4b78ebb488 Mon Sep 17 00:00:00 2001 From: mikecoomber <58986130+mikecoomber@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:05:38 +0100 Subject: [PATCH] fix(UX-1241): Fixed inkwell on ZetaStepper (#190) refactor: Optimized ZetaStepper to be made up of sub-components. test: Wrote tests for ZetaStepper --- .../lib/pages/components/stepper_example.dart | 47 +- .../pages/components/stepper_widgetbook.dart | 15 - lib/src/components/components.dart | 2 +- lib/src/components/stepper/stepper.dart | 641 +++++++++++------- test/TESTING_README.md | 6 + .../golden/stepper_horizontal_complete.png | Bin 0 -> 4480 bytes .../golden/stepper_horizontal_incomplete.png | Bin 0 -> 4402 bytes .../stepper_horizontal_step_disabled.png | Bin 0 -> 5115 bytes .../golden/stepper_vertical_complete.png | Bin 0 -> 6646 bytes .../golden/stepper_vertical_incomplete.png | Bin 0 -> 6382 bytes .../golden/stepper_vertical_step_disabled.png | Bin 0 -> 6455 bytes test/src/components/stepper/stepper_test.dart | 532 +++++++++++++++ 12 files changed, 929 insertions(+), 314 deletions(-) create mode 100644 test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_horizontal_complete.png create mode 100644 test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_horizontal_incomplete.png create mode 100644 test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_horizontal_step_disabled.png create mode 100644 test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_vertical_complete.png create mode 100644 test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_vertical_incomplete.png create mode 100644 test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_vertical_step_disabled.png create mode 100644 test/src/components/stepper/stepper_test.dart diff --git a/example/lib/pages/components/stepper_example.dart b/example/lib/pages/components/stepper_example.dart index ac7a2451..b1f8bd84 100644 --- a/example/lib/pages/components/stepper_example.dart +++ b/example/lib/pages/components/stepper_example.dart @@ -12,19 +12,9 @@ class StepperExample extends StatefulWidget { } class _StepperExampleState extends State { - int _sharpHorizontalStep = 0; + int _horistonalStep = 0; int _verticalStep = 0; - ZetaStepType _getForStepIndex({ - required int currentStep, - required int stepIndex, - }) { - if (currentStep == stepIndex) return ZetaStepType.enabled; - if (currentStep > stepIndex) return ZetaStepType.complete; - - return ZetaStepType.disabled; - } - @override Widget build(BuildContext context) { return ExampleScaffold( @@ -35,32 +25,17 @@ class _StepperExampleState extends State { SizedBox( height: 150, child: ZetaStepper( - currentStep: _sharpHorizontalStep, - onStepTapped: (index) => setState(() => _sharpHorizontalStep = index), + currentStep: _horistonalStep, + onStepTapped: (index) => setState(() => _horistonalStep = index), steps: [ ZetaStep( - type: _getForStepIndex( - currentStep: _sharpHorizontalStep, - stepIndex: 0, - ), title: Text("Title"), - content: Text("Content"), ), ZetaStep( - type: _getForStepIndex( - currentStep: _sharpHorizontalStep, - stepIndex: 1, - ), title: Text("Title 2"), - content: Text("Content 2"), ), ZetaStep( - type: _getForStepIndex( - currentStep: _sharpHorizontalStep, - stepIndex: 2, - ), title: Text("Title 3"), - content: Text("Content 3"), ), ], ), @@ -73,31 +48,17 @@ class _StepperExampleState extends State { onStepTapped: (index) => setState(() => _verticalStep = index), steps: [ ZetaStep( - type: _getForStepIndex( - currentStep: _verticalStep, - stepIndex: 0, - ), title: Text("Title"), subtitle: Text("Step Number"), - content: Text("Content"), ), ZetaStep( - type: _getForStepIndex( - currentStep: _verticalStep, - stepIndex: 1, - ), title: Text("Title 2"), subtitle: Text("Step Number"), - content: Text("Content 2"), + disabled: true, ), ZetaStep( - type: _getForStepIndex( - currentStep: _verticalStep, - stepIndex: 2, - ), title: Text("Title 3"), subtitle: Text("Step Number"), - content: Text("Content 3"), ), ], ), diff --git a/example/widgetbook/pages/components/stepper_widgetbook.dart b/example/widgetbook/pages/components/stepper_widgetbook.dart index 6744143f..082e4590 100644 --- a/example/widgetbook/pages/components/stepper_widgetbook.dart +++ b/example/widgetbook/pages/components/stepper_widgetbook.dart @@ -7,13 +7,6 @@ import '../../utils/scaffold.dart'; Widget stepperUseCase(BuildContext context) { int currentStep = 0; - ZetaStepType getForStepIndex(int stepIndex) { - if (currentStep == stepIndex) return ZetaStepType.enabled; - if (currentStep > stepIndex) return ZetaStepType.complete; - - return ZetaStepType.disabled; - } - final type = context.knobs.list( label: "Type", options: [ @@ -24,8 +17,6 @@ Widget stepperUseCase(BuildContext context) { labelBuilder: (type) => type.name, ); - final enabledContent = context.knobs.boolean(label: 'Enabled Content', initialValue: true); - return WidgetbookScaffold( builder: (context, _) => StatefulBuilder( builder: (context, setState) { @@ -40,19 +31,13 @@ Widget stepperUseCase(BuildContext context) { type: type, steps: [ ZetaStep( - type: getForStepIndex(0), title: Text("Title"), - content: enabledContent ? Text("Content") : null, ), ZetaStep( - type: getForStepIndex(1), title: Text("Title 2"), - content: enabledContent ? Text("Content 2") : null, ), ZetaStep( - type: getForStepIndex(2), title: Text("Title 3"), - content: enabledContent ? Text("Content 3") : null, ), ], ), diff --git a/lib/src/components/components.dart b/lib/src/components/components.dart index ab887229..b1e5eb25 100644 --- a/lib/src/components/components.dart +++ b/lib/src/components/components.dart @@ -45,7 +45,7 @@ export 'segmented_control/segmented_control.dart'; export 'select_input/select_input.dart'; export 'slider/slider.dart'; export 'snack_bar/snack_bar.dart'; -export 'stepper/stepper.dart'; +export 'stepper/stepper.dart' hide HorizontalStep, StepDivider, StepIcon, VerticalStep; export 'stepper_input/stepper_input.dart' hide ZetaStepperInputState; export 'switch/zeta_switch.dart'; export 'tabs/tab.dart'; diff --git a/lib/src/components/stepper/stepper.dart b/lib/src/components/stepper/stepper.dart index b118d81f..069f8307 100644 --- a/lib/src/components/stepper/stepper.dart +++ b/lib/src/components/stepper/stepper.dart @@ -6,6 +6,26 @@ import '../../../zeta_flutter.dart'; /// steps. Steppers are particularly useful in the case of forms where one step /// requires the completion of another one, or where multiple steps need to be /// completed in order to submit the whole form. +/// +/// The steppers current step is managed through the [currentStep] property. +/// To change it, store this value in state and change it with the [onStepTapped] callback. +/// The stored value can then be used to update content depending on the selected step. +/// +/// ```dart +/// ZetaStepper( +/// steps: [ +/// ZetaStep(title: Text('Step 1')), +/// ZetaStep(title: Text('Step 2')), +/// ZetaStep(title: Text('Step 3')), +/// ], +/// currentStep: currentStep, +/// onStepTapped: (step) { +/// setState(() { +/// currentStep = step; +/// }); +/// }, +/// ) +/// ``` /// {@category Components} /// /// Figma: https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=3420-67488&node-type=canvas&m=dev @@ -52,325 +72,418 @@ class ZetaStepper extends ZetaStatefulWidget { super.debugFillProperties(properties); properties ..add(IterableProperty('steps', steps)) - ..properties.add(IntProperty('currentStep', currentStep)) + ..add(IntProperty('currentStep', currentStep)) ..add(EnumProperty('type', type)) ..add( ObjectFlagProperty?>.has( 'onStepTapped', onStepTapped, ), - ) - ..properties.add(DiagnosticsProperty('rounded', rounded)); + ); } } class _ZetaStepperState extends State with TickerProviderStateMixin { - late List _keys; + bool _isLast(int index) { + return widget.steps.length - 1 == index; + } + + bool _isComplete(int index) { + return widget.currentStep > index; + } @override - void initState() { - super.initState(); - _keys = List.generate( - widget.steps.length, - (_) => GlobalKey(), + Widget build(BuildContext context) { + return ZetaRoundedScope( + rounded: context.rounded, + child: switch (widget.type) { + ZetaStepperType.vertical => IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widget.steps + .map( + (step) => VerticalStep( + step: step, + index: widget.steps.indexOf(step), + completed: _isComplete(widget.steps.indexOf(step)), + isLast: _isLast(widget.steps.indexOf(step)), + onStepTapped: !step.disabled ? () => widget.onStepTapped?.call(widget.steps.indexOf(step)) : null, + ), + ) + .toList(), + ), + ), + ZetaStepperType.horizontal => Material( + color: Colors.transparent, + child: Container( + margin: EdgeInsets.symmetric(horizontal: Zeta.of(context).spacing.xl_2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + for (final step in widget.steps) ...[ + HorizontalStep( + step: step, + index: widget.steps.indexOf(step), + completed: _isComplete(widget.steps.indexOf(step)), + onStepTapped: !step.disabled ? () => widget.onStepTapped?.call(widget.steps.indexOf(step)) : null, + ), + if (!_isLast(widget.steps.indexOf(step))) + Expanded( + child: StepDivider( + disabled: step.disabled, + type: ZetaStepperType.horizontal, + completed: _isComplete(widget.steps.indexOf(step)), + ), + ), + ], + ], + ), + ), + ) + }, ); } +} - ZetaColors get _colors => Zeta.of(context).colors; +Color _getElementColor(BuildContext context, bool disabled, bool completed) { + final colors = Zeta.of(context).colors; + Color boxColor = colors.primary; - bool _isFirst(int index) { - return index == 0; + if (disabled) { + boxColor = colors.iconDisabled; + } else if (completed) { + boxColor = colors.surfacePositive; } - bool _isLast(int index) { - return widget.steps.length - 1 == index; - } + return boxColor; +} - bool _isCurrent(int index) { - return widget.currentStep == index; - } +/// The icon that represents a step in the [ZetaStepper] widget. +@visibleForTesting +@protected +class StepIcon extends StatelessWidget { + /// Creates a step icon for the [ZetaStepper] widget. + const StepIcon({ + required this.index, + required this.completed, + required this.disabled, + required this.type, + super.key, + }); + + /// The index of the step in the list of steps. + final int index; - Widget _buildHorizontalIcon(int index) { + /// Whether the step is completed. + final bool completed; + + /// Whether the step is disabled. + final bool disabled; + + /// The size of the icon. + final ZetaStepperType type; + + @override + Widget build(BuildContext context) { final rounded = context.rounded; + final colors = Zeta.of(context).colors; + final spacing = Zeta.of(context).spacing; - return SizedBox( - width: Zeta.of(context).spacing.xl_4, - height: Zeta.of(context).spacing.xl_4, - child: AnimatedContainer( - curve: Curves.fastOutSlowIn, - duration: kThemeAnimationDuration, - decoration: BoxDecoration( - color: _getColorForType(widget.steps[index].type), - shape: rounded ? BoxShape.circle : BoxShape.rectangle, - ), - child: Center( - child: switch (widget.steps[index].type) { - ZetaStepType.complete => ZetaIcon( + final size = switch (type) { + ZetaStepperType.horizontal => spacing.xl_4, + ZetaStepperType.vertical => spacing.xl_6, + }; + + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: _getElementColor(context, disabled, completed), + shape: rounded ? BoxShape.circle : BoxShape.rectangle, + ), + child: Center( + child: completed && !disabled + ? ZetaIcon( ZetaIcons.check_mark, - color: _colors.textInverse, - ), - ZetaStepType.enabled || ZetaStepType.disabled => Text( + color: colors.textInverse, + ) + : Text( (index + 1).toString(), style: ZetaTextStyles.labelLarge.copyWith( - color: _colors.textInverse, + color: colors.textInverse, ), ), - }, - ), ), ); } - Widget _getVerticalIcon(int index) { - return SizedBox( - width: Zeta.of(context).spacing.xl_8, - height: Zeta.of(context).spacing.xl_8, - child: AnimatedContainer( - curve: Curves.fastOutSlowIn, - duration: kThemeAnimationDuration, - decoration: BoxDecoration( - color: _getColorForType(widget.steps[index].type), - shape: context.rounded ? BoxShape.circle : BoxShape.rectangle, - ), - child: Center( - child: switch (widget.steps[index].type) { - ZetaStepType.complete => ZetaIcon( - ZetaIcons.check_mark, - color: _colors.textInverse, - ), - ZetaStepType.enabled || ZetaStepType.disabled => Text( - (index + 1).toString(), - style: ZetaTextStyles.titleLarge.copyWith( - color: _colors.textInverse, - ), - ), - }, - ), + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(IntProperty('index', index)) + ..add(DiagnosticsProperty('completed', completed)) + ..add(DiagnosticsProperty('disabled', disabled)) + ..add(EnumProperty('type', type)); + } +} + +/// A divider that separates steps in the [ZetaStepper] widget. +@visibleForTesting +@protected +class StepDivider extends StatelessWidget { + /// Creates a step divider for the [ZetaStepper] widget. + const StepDivider({ + super.key, + required this.type, + required this.disabled, + required this.completed, + }); + + /// Disables the divider and changes its color. + final bool disabled; + + /// Changes the color of the divider to indicate completion. + final bool completed; + + /// The type of the divider. + final ZetaStepperType type; + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + final spacing = Zeta.of(context).spacing; + + Color color = colors.borderPrimary; + + if (disabled) { + color = colors.borderDefault; + } else if (completed) { + color = colors.borderPositive; + } + + return Container( + margin: switch (type) { + ZetaStepperType.horizontal => EdgeInsets.only( + top: spacing.xl_3, + right: spacing.small, + left: spacing.small, + ), + ZetaStepperType.vertical => EdgeInsets.only( + top: spacing.minimum, + ), + }, + width: switch (type) { + ZetaStepperType.horizontal => double.infinity, + ZetaStepperType.vertical => spacing.minimum, + }, + height: switch (type) { + ZetaStepperType.horizontal => ZetaBorders.medium, + ZetaStepperType.vertical => spacing.xl_8, + }, + decoration: BoxDecoration( + borderRadius: Zeta.of(context).radius.full, + color: color, ), ); } - Widget _getHeaderText(int index) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - AnimatedDefaultTextStyle( - style: switch (widget.steps[index].type) { - ZetaStepType.enabled || ZetaStepType.complete => ZetaTextStyles.bodySmall.copyWith( - color: _colors.textDefault, - ), - ZetaStepType.disabled => ZetaTextStyles.bodySmall.copyWith( - color: _colors.textDisabled, - ), - }, - maxLines: 1, - duration: kThemeAnimationDuration, - curve: Curves.fastOutSlowIn, - child: widget.steps[index].title, - ), - ], - ); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('disabled', disabled)) + ..add(DiagnosticsProperty('completed', completed)) + ..add(EnumProperty('type', type)); } +} - Widget _getVerticalHeader(int index) { - final subtitle = widget.steps[index].subtitle; +/// A horizontal step in the [ZetaStepper] widget. +@visibleForTesting +@protected +class HorizontalStep extends StatelessWidget { + /// Creates a horizontal step in the [ZetaStepper] widget. + const HorizontalStep({ + required this.step, + required this.index, + required this.completed, + this.onStepTapped, + super.key, + }); - return Container( - margin: EdgeInsets.only(top: _isFirst(index) ? 0.0 : Zeta.of(context).spacing.xl_2), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( + /// The step that this widget represents. + final ZetaStep step; + + /// The index of the step in the list of steps. + final int index; + + /// Whether the step is completed. + final bool completed; + + /// The callback called when the step is tapped. + final VoidCallback? onStepTapped; + + @override + Widget build(BuildContext context) { + final spacing = Zeta.of(context).spacing; + final colors = Zeta.of(context).colors; + final radius = Zeta.of(context).radius; + + return Semantics( + label: step.semanticLabel, + excludeSemantics: step.semanticLabel != null, + child: InkWell( + onTap: onStepTapped, + canRequestFocus: !step.disabled, + borderRadius: radius.minimal, + child: Padding( + padding: EdgeInsets.only(left: spacing.small, right: spacing.small, bottom: spacing.small), + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - _getVerticalIcon(index), - Container( - margin: EdgeInsets.only(top: Zeta.of(context).spacing.minimum), - width: Zeta.of(context).spacing.minimum, - height: Zeta.of(context).spacing.xl_8, - decoration: BoxDecoration( - borderRadius: Zeta.of(context).radius.full, - color: switch (widget.steps[index].type) { - ZetaStepType.complete => _colors.green.shade50, - ZetaStepType.disabled => _colors.borderSubtle, - ZetaStepType.enabled => _colors.blue.shade50, - }, + Center( + child: Padding( + padding: EdgeInsets.symmetric( + vertical: spacing.medium, + ), + child: StepIcon( + index: index, + completed: completed, + disabled: step.disabled, + type: ZetaStepperType.horizontal, + ), ), ), - ], - ), - Expanded( - child: Container( - margin: EdgeInsets.symmetric(horizontal: Zeta.of(context).spacing.xl_2), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (subtitle != null) - AnimatedDefaultTextStyle( - style: ZetaTextStyles.bodyMedium.copyWith( - color: _getColorForType(widget.steps[index].type), - ), - maxLines: 1, - duration: kThemeAnimationDuration, - curve: Curves.fastOutSlowIn, - child: subtitle, - ), - AnimatedDefaultTextStyle( - style: switch (widget.steps[index].type) { - ZetaStepType.enabled || ZetaStepType.complete => ZetaTextStyles.titleLarge.copyWith( - color: _colors.textDefault, - ), - ZetaStepType.disabled => ZetaTextStyles.titleLarge.copyWith( - color: _colors.textDisabled, - ), - }, - maxLines: 1, - duration: kThemeAnimationDuration, - curve: Curves.fastOutSlowIn, - child: widget.steps[index].title, - ), - ], + DefaultTextStyle( + style: ZetaTextStyles.bodySmall.copyWith( + color: step.disabled ? colors.textDisabled : colors.textDefault, + ), + child: step.title, ), - ), + ], ), - ], + ), ), ); } - Widget _getVerticalBody(int index) { - return Stack( - children: [ - AnimatedCrossFade( - firstChild: Container(height: 0), - secondChild: widget.steps[index].content ?? const Nothing(), - firstCurve: const Interval(0, 0.6, curve: Curves.fastOutSlowIn), - secondCurve: const Interval(0.4, 1, curve: Curves.fastOutSlowIn), - sizeCurve: Curves.fastOutSlowIn, - crossFadeState: _isCurrent(index) ? CrossFadeState.showSecond : CrossFadeState.showFirst, - duration: kThemeAnimationDuration, - ), - ], - ); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('step', step)) + ..add(IntProperty('index', index)) + ..add(DiagnosticsProperty('completed', completed)) + ..add(ObjectFlagProperty.has('onStepTapped', onStepTapped)); } +} - Color _getColorForType(ZetaStepType type) { - return switch (type) { - ZetaStepType.complete => _colors.surfacePositive, - ZetaStepType.disabled => _colors.cool.shade50, - ZetaStepType.enabled => _colors.primary, - }; - } +/// A vertical step in the [ZetaStepper] widget. +@visibleForTesting +@protected +class VerticalStep extends StatelessWidget { + /// Creates a vertical step in the [ZetaStepper] widget. + const VerticalStep({ + required this.step, + required this.index, + required this.completed, + required this.isLast, + this.onStepTapped, + super.key, + }); + + /// The step that this widget represents. + final ZetaStep step; + + /// The index of the step in the list of steps. + final int index; + + /// Whether the step is completed. + final bool completed; + + /// Whether the step is the last one in the list. + final bool isLast; + + /// The callback called when the step is tapped. + final VoidCallback? onStepTapped; @override Widget build(BuildContext context) { - return ZetaRoundedScope( - rounded: context.rounded, - child: switch (widget.type) { - ZetaStepperType.vertical => Column( - children: [ - for (int index = 0; index < widget.steps.length; index += 1) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - key: _keys[index], - children: [ - InkResponse( - containedInkWell: true, - borderRadius: Zeta.of(context).radius.minimal, - onTap: widget.onStepTapped != null ? () => widget.onStepTapped?.call(index) : null, - canRequestFocus: widget.steps[index].type != ZetaStepType.disabled, - child: _getVerticalHeader(index), - ), - _getVerticalBody(index), - ], - ), - ], + final spacing = Zeta.of(context).spacing; + final colors = Zeta.of(context).colors; + + return Semantics( + label: step.semanticLabel, + excludeSemantics: step.semanticLabel != null, + child: InkWell( + borderRadius: Zeta.of(context).radius.minimal, + onTap: onStepTapped, + canRequestFocus: !step.disabled, + child: Container( + padding: EdgeInsets.all( + spacing.medium, ), - ZetaStepperType.horizontal => Builder( - builder: (context) { - final children = [ - for (int index = 0; index < widget.steps.length; index += 1) ...[ - InkResponse( - onTap: widget.onStepTapped != null ? () => widget.onStepTapped?.call(index) : null, - canRequestFocus: widget.steps[index].type != ZetaStepType.disabled, - child: Column( - children: [ - Center( - child: Padding( - padding: EdgeInsets.symmetric( - vertical: Zeta.of(context).spacing.medium, - ), - child: _buildHorizontalIcon(index), - ), - ), - _getHeaderText(index), - ], - ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + StepIcon( + index: index, + completed: completed, + disabled: step.disabled, + type: ZetaStepperType.vertical, ), - if (!_isLast(index)) - Expanded( - child: Container( - key: Key('line$index'), - margin: EdgeInsets.only( - top: Zeta.of(context).spacing.xl_3, - right: Zeta.of(context).spacing.large, - left: Zeta.of(context).spacing.large, - ), - height: ZetaBorders.medium, - decoration: BoxDecoration( - borderRadius: Zeta.of(context).radius.full, - color: switch (widget.steps[index].type) { - ZetaStepType.complete => _colors.green.shade50, - ZetaStepType.disabled => _colors.borderSubtle, - ZetaStepType.enabled => _colors.blue.shade50, - }, - ), - ), + if (!isLast) + StepDivider( + type: ZetaStepperType.vertical, + completed: completed, + disabled: step.disabled, ), ], - ]; - - final List stepPanels = []; - for (int i = 0; i < widget.steps.length; i += 1) { - stepPanels.add( - Visibility( - maintainState: true, - visible: i == widget.currentStep, - child: widget.steps[i].content ?? const Nothing(), - ), - ); - } - - return Column( + ), + SizedBox(width: spacing.xl_2), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Material( - color: Colors.transparent, - child: Container( - margin: EdgeInsets.symmetric(horizontal: Zeta.of(context).spacing.xl_2), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: children, + if (step.subtitle != null) + AnimatedDefaultTextStyle( + style: ZetaTextStyles.bodyMedium.copyWith( + color: _getElementColor(context, step.disabled, completed), ), - ), - ), - Expanded( - child: AnimatedSize( - curve: Curves.fastOutSlowIn, + maxLines: 1, duration: kThemeAnimationDuration, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: stepPanels, - ), + curve: Curves.fastOutSlowIn, + child: step.subtitle!, + ), + DefaultTextStyle( + style: ZetaTextStyles.titleLarge.copyWith( + color: step.disabled ? colors.textDisabled : colors.textDefault, ), + maxLines: 1, + child: step.title, ), ], - ); - }, + ), + ], ), - }, + ), + ), ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('step', step)) + ..add(IntProperty('index', index)) + ..add(DiagnosticsProperty('completed', completed)) + ..add(DiagnosticsProperty('isLast', isLast)) + ..add(ObjectFlagProperty.has('onStepTapped', onStepTapped)); + } } /// Zeta step used in [ZetaStepper]. The step can have a title and subtitle, @@ -380,12 +493,18 @@ class ZetaStep { /// Creates a step for a [ZetaStepper]. const ZetaStep({ required this.title, - this.content, + @Deprecated('Steps no longer manage their own content. ' 'Deprecated as of 0.16.1') this.content, this.subtitle, + this.disabled = false, + this.semanticLabel, + @Deprecated( + 'To disable a step, set its disabled prop to true. To complete a step, set the currentStep prop on the stepper greater than the step index. ' + 'Deprecated as of 0.16.1') this.type = ZetaStepType.disabled, }); /// The content of the step that appears below the [title] and [subtitle]. + @Deprecated('Steps no longer manage their own content. ' 'Deprecated as of 0.16.1') final Widget? content; /// The subtitle of the step that appears above the title. @@ -394,12 +513,24 @@ class ZetaStep { /// The title of the step that typically describes it. final Widget title; + /// The semantic label of the step that is read by screen readers. + final String? semanticLabel; + + /// Whether the step is disabled and does not react to taps. + final bool disabled; + /// The type of the step which determines the styling of its components /// and whether steps are interactive. + @Deprecated( + 'To disable a step, set its disabled prop to true. To complete a step, set the activeStep prop on the stepper greater than the step index. ' + 'Deprecated as of 0.16.1') final ZetaStepType type; } /// The type of a [ZetaStep] which is used to control the style of the circle and text. +@Deprecated( + 'To disable a step, set its disabled prop to true. To complete a step, set the activeStep prop on the stepper greater than the step index. ' + 'Deprecated as of 0.16.1') enum ZetaStepType { /// A step that is currently selected with primary color icon enabled, diff --git a/test/TESTING_README.md b/test/TESTING_README.md index a98b8042..0bcb169e 100644 --- a/test/TESTING_README.md +++ b/test/TESTING_README.md @@ -59,6 +59,7 @@ void main() { }); group('Accessibility Tests', () {}); + group('Content Tests', () { final debugFillProperties = { '': '', @@ -68,12 +69,17 @@ void main() { debugFillProperties, ); }); + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { goldenTest(goldenFile, widget, widgetType, 'PNG_FILE_NAME'); }); + group('Performance Tests', () {}); } ``` diff --git a/test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_horizontal_complete.png b/test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_horizontal_complete.png new file mode 100644 index 0000000000000000000000000000000000000000..23ee538b6848a2eb7fd793b675cf2377658268b7 GIT binary patch literal 4480 zcmeHKX;2eq82$ngC?3c-MPeXmsZFa=JP8a(5VRC4(ppC-9)Q87R3b#m5n&0%YAjdq z0F*1EoqATHL_)Yi=nPpDg;qq4KtNVVIYL;21QJ3T6MyuNqjmbDKl1(A{oZ+>eUI%)M)^0*930cx+l3r-nZ{Ghx#LoQj0drKiv7KC@g=prenuf-+QOCZVY7B)+AqE zlym&o(;p{ani4x-8_Ul*7+Mz>S-pIy1u1fofGQsd4GUXQTNontXz_%3 zN#um_>dCWckurS!E;6m7I?X?*~n>+DNt`mw2}OF0yrGsdir^)FM27arU!oUBph zmP!gp&lsS%aT*gJr3o{8W`DFkNf)YpE~SsvYv9HfZciSye1A1{|IANHaYvz$`SQl? z*+qcAXMh|}vwp&c8wC#n6%6kK?;G6g=sS`stE!|XaGHy>`E4)Ra; z`uz+mH@D*m*S-i zYBYbX%Ar$9kZ+Nym|7)z(JAe4>cHwkg31x&R%CoSjL&2BCHy7oN@?HH&EV9G6rr{L zDyj_QkY#lEBHe2|T&Ut81BYBZ6{175)WH;8eyL^LIzwwoE|`sCfo}2=+5~mVWNFd; zRj`(2GbA%xB(Z#OUS3=VaFkS8E<~YEv1h9dB&eoP(xjV3Xayv4=oXH=m$yOOsZ$Vn zUib?nJ?M-P)H_!zp)%ao#~R~!>*LHkG;iPxb&Kk{eIjyW`lqnX_DI#Tv<*A|;ij`) zg&*XR)f~Fyqxs;=acdY)x`T0wl&51?g%($V1TN|FamysFq+}E2?ner!=E`r4%~M)L zWuwm=sX5^U!I27{&Ta;8n>=?1BDPI8B5Y$$UW(oZC$d1v*Qq)k>*j6*dEg_gU**@I zu|=FLpKarekCP5e4&H0E7=CCt>?=fHu{}4nXFHPZa#bcfDyc}Q8&!OIziolN9{P!d zjQJC+K~nEVwmj#;?1+ZlmyccK|#wl)sqiCc0wy;#Q-qu~6iD;I9CKLWmGk zDID57WYI{)LRTEuTekq2YUl5S4yT_pQl>Z{U9X8a@`8o`N)BHyoVqolzK`L>IK~!rPmt`2Dc-pa8exiZKS9A^YA*mrU#teWctQ7!xwk!1_{n@)1Ux})| zkl{xBTTA-uYj~@PV1oO;#oRP?$<(EH_}ntl#zY$vZT_Q?Y#L{8__;HwjY)0(Pimti YF5_&qyvU5+0rV?y`>t)}ei0de1GE*d8~^|S literal 0 HcmV?d00001 diff --git a/test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_horizontal_incomplete.png b/test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_horizontal_incomplete.png new file mode 100644 index 0000000000000000000000000000000000000000..11ed680d4a4fa83bc2ca23e1aca1fb0c64b439e5 GIT binary patch literal 4402 zcmeHKYfMvT7(OjXi^?rCVIW!}8m0p}X|!UYMZ6%>fblja-q7Wd~e?Keed&p z&-l)0MMaWDoR%ydKW>sZkB9Z5`-+1ow?}`UDSH0r4Z~3gDIedJ} zr;o1{Z~4IsvK9oK$;sK_^~nAtboi6hl)`JroNrWJ+%Ori_WsuRyWtzMjvgiyKs=T^ zp}NE&fN-1Ytu;W;)J)HqJ8Bl4z9?bIT9lWjRH_7|NY_N__&TI6#eOa_I-$@2K()jI zK)!=30QetzPcYY$jS}8!@7b*`RO01Dq6Q-D%Ziq@Gm|5reoz5I$$ACUPNR|&;cmwK z-2(M2CK#NpJQBH6(ifxW?rLOXVq!TQPSgBBmlS+B1V)mZNE}l|(P$x&(JG5ekgC$@ zEaUJ{S&Le5-$HzdTO!q9)#QO1X1Z^AksN1OhmMxDda&ZX%gDI05& z%Lmw3j0<)$&`+pGdk@akvn9^2M9~j7@@cv*&@H5tQlFK0VN^%^YP8!O4~}G#(1V^0 zD@h~R{2twt9xoXu&D3l`QNrR>hG?ca2a|3qDLK2s)ALz}+SmyC!e4DBenjy)Rzlk* zN8{48aN%|JW{UQE3W=Q*E7EnO%Gy&e`bYT)>_Xl8gdV9w#T0$lbTaNqW>R_rp@PLE zlI#YVE2JcRjiz3R5Ix}GjXhOMkh@4k#7E9qZKz+rf-$Td;PLqB+PO*m%+q}=e*9<+ z-0lMtZDvltB1hefgXb(e!8Bcq;Z(UzZON2{qEq7;!UaK&X>d@C8v5ejQB%ajM_oE1IV?;dQk@Fr+0_IksnA@7Q3t)`cxN|YfF3o+whL}i1D^ZMILFA&6V zm@T$pb8%7qtN0lRe)6m&`!|`f8B;{s7)pExJd&xM^vRKb4f)AuU0f#P~U?Z90VH+q$fMku>SH_4v__iJ%!1?@(~l)XQLAcjaOKn93@dyWddC?(yv$Zlm1trNnelv*U*La6*r)SiZ@bKw|7ipu4QC0I*JJD-uIXJ? zNMs;;!X+MQTv0@(yZ=DOvX9KGi$H;zg^tEznT4d<-rNiC2g{)wi`{q46I}D9ZgPSC)ZjcM00Ki+c263`Z ztUcmtZvFtky|EHSTYh|y7B{|*E@x5d=eN0h3F|b%OJi{Vn4zs(&E0rjUnkc*CY4OJ zikjmqQvcgV|EM!euh*AU;SmE4#i-unyC3e*=1kWQc#oM`GP4AH{O6BZHfGtFW%E&g m4Ca2DW z{_4E@zx4`3LxRkf!3Y@PZ$6|900&D+Qb<2+&p*wG-wRc!h!+-TEERC_+XS4 z5FB9wUO6TwE(3tYo!~vYBQmOHM937f=4jt%`poR?U~pA^FbR9g%59PKYU}=1ob}E9 z4{)Zn<`J(C#ce&ZZqu1Zs~wErtg#4yoM|Zvb|@}7^=GSt)0F*=s?xiA-NWuj9-G;B z@{J$L1jF2LU~EGh=S)gkc`)xLqVc^VbGk|(-FB5I4l1?#urMO=lz2i!So|%pH$!8R zFKB9sCMyBJVQ(II?`miS0GC%e0D%7$zyJV_8<_yW%Ju#LkhjLb5CES1Cba3MZ{AP0 z({gWB@7fhB&b<{5-83;Z+cD?m$_RxZ7{^rp7#r(m4C&(3Bir?==drPFySU0#kT_Nc z7;{!x?6vSh&p#Xz!dD(_Nb(|6C~!<^M3+xk6w)pJ!LX7gJiCZ<+xd~XCE-J>TuRQz zKInR--0g&SY_OV=S65T$ddWdD^T_~%<`dej&p)^LmYK-NO|O;oncU-gk+DAWNT~Ee zfeUYXdhURh6p3z^wJ+8S=MD^^PTb}x5{;r!Y68>Ie`GhFHf66qB#6>sC?O#(vK>@= z+q>PfgVBqlmx7w^T*+UZfX!u9>*c+wf<-80VA2^?TvirI_|!kccyEPmT!-J2x4MVr z5|p=KQ%=5+^{MQ6miEKjjWKfF3|0Fqna`iS{1@ZvbS%EjF8j+oPN^!~7?Uc!&c=!9 zBS)$HPgk3vuT0g17{l>DncI?@pmvqb-}g;iPZtf*)>2aSvJBC=;tj^vYswoq+{98j z6|_#1ol-iSE^e1cp%xME4|F>Bgx!t|xpt9;)$I*~D^LbX1$&U|ikejj+G$)wpIpFIxUO725 zyPWZZY&F)Xr)!&^A2PdoFt#CUrM(|+N?DmTT`1=*JhVEVkaoeCQsF6nTpjoNeRzN8o#(JDtbV>6 zSFcbHoSC3|#Pfyd?u-SD-raXwC{%ZXaI|YL#E@NCJ;{|3--e5db|q;PFrxYwzE11G zQneA$jEmB=nmHBR7K7vqfr?%hJ*NNhGNNjn8|d;lzH-`$TTyfK2R5YrMshV_^ykm7 zH;ZH`K8oSe;|jp}85nJNn-aot;(FAO3pt|2xP}l;yTP&@bwgxofeQ;>ll|#c6K}?u zUFU2tvSpq%@!gLUt!#5E5A*KjZnDBuC5n1z0%7meYD>|ye3uIxHe2voK(whb4OJ6f zo(CE!eXuREirgq|N^LjuoU)izWv2A_mS^erJ3r3Koz<$P!i zSNU16q63NTgn`qGbB;8Wc5Ae8ywA2#tT`c372So+5!ctl3O46?J0KXR*#2MZ4h?=h z`H%X;28|Rc-<)3-&d%8+?_@;ng=BfGiLB-&&UmG%;}ToxMk8hyf9hk=Gy4M&Og)p0 zi~ib9+nMjvQf+p8ZCXuWTR0$8jj^%CY@Jd)kpx-9yj00grKtH^NY=#^ zYuSM|a5x1f!Ud(+^*jYf5nVepqYb=fs>|SXXoZ^XekG~~%@w|Q`iO1ivQHs8f>@^= zEj15J5~*|;RT)fo{DftXcMCZA-VUjs@610#_2HOAuSZa9Gy>TkwG8sI_mZr$&OKf< z-w=lBoR|`3O4QXHgZ4jyy{&Rcbp?mJ)OSZbNuZM5PZ4d6DD}NhWs}ZD46>IrZ*V6j}_6zuP!j8Ax^Y64GGu;lB>0cC{EAFG!$H zL^Z+FaQqKdlTKW*uMa}so?71@KQ?(pV0V`z7SUXWy(iL$JwI^CK1hztFO*}yGp@B& z;xd^EE=kxjGXau3C)NOw8|GOf77p?N$wGh+E~8DSx)5Spb))HhxB@pe9Iz#Bwo3er zSHK_)gf6N}%CuZ66|PR)-UIIESXRk`n1q}CuwGRjUhcMi0PHjbfnWsfMcoG_h;C1h zZ5s)V#bz8f)0XXiw>&{C@cvaBKfAB~kBay^#PiqC7@pE#I9<_J4?7lykRho!7b1eORa5m+LyL|}=)5`iTGO9cKu2+R>nbM36xit|2#f5d>` Mz>qz(fQ0jZ15dEO;s5{u literal 0 HcmV?d00001 diff --git a/test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_vertical_complete.png b/test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_vertical_complete.png new file mode 100644 index 0000000000000000000000000000000000000000..e8b697696531566da3845f0f9ce61d1f300e187a GIT binary patch literal 6646 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 M6@7T2*6oM?4ICQc{Qv*} literal 0 HcmV?d00001 diff --git a/test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_vertical_incomplete.png b/test/src/components/ENTER_PARENT_FOLDER (e.g. button)/golden/stepper_vertical_incomplete.png new file mode 100644 index 0000000000000000000000000000000000000000..de3b2b7052655d5c90bdde8ea7372f1516f940cb GIT binary patch literal 6382 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 literal 0 HcmV?d00001 diff --git a/test/src/components/stepper/stepper_test.dart b/test/src/components/stepper/stepper_test.dart new file mode 100644 index 00000000..f8341f0c --- /dev/null +++ b/test/src/components/stepper/stepper_test.dart @@ -0,0 +1,532 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/src/components/stepper/stepper.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'; + +void main() { + const String parentFolder = 'ENTER_PARENT_FOLDER (e.g. button)'; + + const goldenFile = GoldenFiles(component: parentFolder); + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('ZetaStepper Accessibility Tests', () { + testWidgets('Horizontal stepper meets accessibility requirements', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + TestApp( + home: ZetaStepper( + steps: const [ZetaStep(title: Text('Title'))], + currentStep: 0, + onStepTapped: (step) {}, + ), + ), + ); + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + handle.dispose(); + }); + + testWidgets('Vertical stepper meets accessibility requirements', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + TestApp( + home: ZetaStepper( + steps: const [ZetaStep(title: Text('Title'))], + currentStep: 0, + type: ZetaStepperType.vertical, + onStepTapped: (step) {}, + ), + ), + ); + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + handle.dispose(); + }); + + testWidgets('Horizontal steps correctly recieve semantic labels', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + TestApp( + home: ZetaStepper( + steps: const [ZetaStep(title: Text('Title'), semanticLabel: 'semantic label')], + currentStep: 0, + onStepTapped: (step) {}, + ), + ), + ); + + expect(find.bySemanticsLabel('semantic label'), findsOneWidget); + + handle.dispose(); + }); + + testWidgets('Vertical steps correctly recieve semantic labels', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + TestApp( + home: ZetaStepper( + steps: const [ZetaStep(title: Text('Title'), semanticLabel: 'semantic label')], + currentStep: 0, + type: ZetaStepperType.vertical, + onStepTapped: (step) {}, + ), + ), + ); + + expect(find.bySemanticsLabel('semantic label'), findsOneWidget); + + handle.dispose(); + }); + }); + + group('ZetaStepper Content Tests', () { + testWidgets('Horizontal stepper renders the correct steps', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaStepper( + steps: const [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2')), + ZetaStep(title: Text('Title 3')), + ], + currentStep: 0, + onStepTapped: (step) {}, + ), + ), + ); + + expect(find.text('Title 1'), findsOneWidget); + expect(find.text('Title 2'), findsOneWidget); + expect(find.text('Title 3'), findsOneWidget); + }); + + testWidgets('Vertical stepper renders the correct steps', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaStepper( + steps: const [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2')), + ZetaStep(title: Text('Title 3')), + ], + type: ZetaStepperType.vertical, + currentStep: 0, + onStepTapped: (step) {}, + ), + ), + ); + + expect(find.text('Title 1'), findsOneWidget); + expect(find.text('Title 2'), findsOneWidget); + expect(find.text('Title 3'), findsOneWidget); + }); + + testWidgets('StepIcon displays the correct text', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepIcon( + completed: false, + disabled: false, + index: 0, + type: ZetaStepperType.horizontal, + ), + ), + ); + + expect(find.text('1'), findsOneWidget); + }); + testWidgets('StepIcon displays the correct icon when completed', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepIcon( + completed: true, + disabled: false, + index: 0, + type: ZetaStepperType.horizontal, + ), + ), + ); + + expect(find.byIcon(ZetaIcons.check_mark_round), findsOneWidget); + }); + + debugFillPropertiesTest( + const ZetaStepper(steps: [], currentStep: 0), + { + 'steps': '[]', + 'currentStep': '0', + 'type': 'horizontal', + 'onStepTapped': 'null', + }, + ); + debugFillPropertiesTest( + const StepIcon(completed: false, disabled: false, index: 0, type: ZetaStepperType.horizontal), + { + 'index': '0', + 'type': 'horizontal', + 'completed': 'false', + 'disabled': 'false', + }, + ); + const step = ZetaStep(title: Text('Title')); + debugFillPropertiesTest( + const HorizontalStep(step: step, index: 0, completed: false), + { + 'step': "Instance of 'ZetaStep'", + 'index': '0', + 'completed': 'false', + 'onStepTapped': 'null', + }, + ); + debugFillPropertiesTest( + const VerticalStep(step: step, index: 0, completed: false, isLast: false), + { + 'step': "Instance of 'ZetaStep'", + 'index': '0', + 'completed': 'false', + 'isLast': 'false', + 'onStepTapped': 'null', + }, + ); + }); + + group('ZetaStepper Dimensions Tests', () { + testWidgets('StepIcon horiztonal has the correct size', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepIcon( + completed: false, + disabled: false, + index: 0, + type: ZetaStepperType.horizontal, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + final spacing = Zeta.of(getBuildContext(tester, Container)).spacing; + + expect(container.constraints?.maxHeight, spacing.xl_4); + expect(container.constraints?.maxWidth, spacing.xl_4); + }); + + testWidgets('StepIcon vertical has the correct size', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepIcon( + completed: false, + disabled: false, + index: 0, + type: ZetaStepperType.vertical, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + final spacing = Zeta.of(getBuildContext(tester, Container)).spacing; + + expect(container.constraints?.maxHeight, spacing.xl_6); + expect(container.constraints?.maxWidth, spacing.xl_6); + }); + + testWidgets('StepDivider horizontal has the correct size', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepDivider( + completed: false, + disabled: false, + type: ZetaStepperType.horizontal, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + expect(container.constraints?.maxHeight, ZetaBorders.medium); + expect(container.constraints?.maxWidth, double.infinity); + }); + + testWidgets('StepDivider vertical has the correct size', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepDivider( + completed: false, + disabled: false, + type: ZetaStepperType.vertical, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + final spacing = Zeta.of(getBuildContext(tester, Container)).spacing; + expect(container.constraints?.maxHeight, spacing.xl_8); + expect(container.constraints?.maxWidth, spacing.minimum); + }); + }); + + group('ZetaStepper Styling Tests', () { + testWidgets( + 'StepIcon has the correct colour when enabled', + (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepIcon( + completed: false, + disabled: false, + index: 0, + type: ZetaStepperType.horizontal, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + expect( + (container.decoration! as BoxDecoration).color, + Zeta.of(getBuildContext(tester, Container)).colors.primary, + ); + }, + ); + testWidgets( + 'StepIcon has the correct colour when completed', + (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepIcon( + completed: true, + disabled: false, + index: 0, + type: ZetaStepperType.horizontal, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + expect( + (container.decoration! as BoxDecoration).color, + Zeta.of(getBuildContext(tester, Container)).colors.surfacePositive, + ); + }, + ); + testWidgets( + 'StepIcon has the correct colour when disabled', + (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepIcon( + completed: true, + disabled: true, + index: 0, + type: ZetaStepperType.horizontal, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + expect( + (container.decoration! as BoxDecoration).color, + Zeta.of(getBuildContext(tester, Container)).colors.iconDisabled, + ); + }, + ); + testWidgets( + 'StepDivider has the correct colour when enabled', + (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepDivider( + completed: false, + disabled: false, + type: ZetaStepperType.horizontal, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + expect( + (container.decoration! as BoxDecoration).color, + Zeta.of(getBuildContext(tester, Container)).colors.borderPrimary, + ); + }, + ); + testWidgets( + 'StepDivider has the correct colour when completed', + (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepDivider( + completed: true, + disabled: false, + type: ZetaStepperType.horizontal, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + expect( + (container.decoration! as BoxDecoration).color, + Zeta.of(getBuildContext(tester, Container)).colors.borderPositive, + ); + }, + ); + testWidgets( + 'StepDivider has the correct colour when disabled', + (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: StepDivider( + completed: true, + disabled: true, + type: ZetaStepperType.horizontal, + ), + ), + ); + + final container = tester.widget(find.byType(Container)); + expect( + (container.decoration! as BoxDecoration).color, + Zeta.of(getBuildContext(tester, Container)).colors.borderDefault, + ); + }, + ); + }); + + group('ZetaStepper Interaction Tests', () { + testWidgets('Horizontal stepper calls onStepTapped when a step is tapped', (WidgetTester tester) async { + int tappedStep = -1; + await tester.pumpWidget( + TestApp( + home: ZetaStepper( + steps: const [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2')), + ZetaStep(title: Text('Title 3')), + ], + currentStep: 0, + onStepTapped: (step) { + tappedStep = step; + }, + ), + ), + ); + + await tester.tap(find.text('Title 2')); + expect(tappedStep, 1); + }); + + testWidgets('Vertical stepper calls onStepTapped when a step is tapped', (WidgetTester tester) async { + int tappedStep = -1; + await tester.pumpWidget( + TestApp( + home: ZetaStepper( + steps: const [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2')), + ZetaStep(title: Text('Title 3')), + ], + type: ZetaStepperType.vertical, + currentStep: 0, + onStepTapped: (step) { + tappedStep = step; + }, + ), + ), + ); + + await tester.tap(find.text('Title 2')); + expect(tappedStep, 1); + }); + }); + + group('ZetaStepper Golden Tests', () { + goldenTest( + goldenFile, + const ZetaStepper( + steps: [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2')), + ZetaStep(title: Text('Title 3')), + ], + currentStep: 0, + ), + 'stepper_horizontal_incomplete', + ); + + goldenTest( + goldenFile, + const ZetaStepper( + steps: [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2')), + ZetaStep(title: Text('Title 3')), + ], + currentStep: 4, + ), + 'stepper_horizontal_complete', + ); + goldenTest( + goldenFile, + const ZetaStepper( + steps: [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2'), disabled: true), + ZetaStep(title: Text('Title 3')), + ], + currentStep: 0, + ), + 'stepper_horizontal_step_disabled', + ); + goldenTest( + goldenFile, + const ZetaStepper( + type: ZetaStepperType.vertical, + steps: [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2')), + ZetaStep(title: Text('Title 3')), + ], + currentStep: 0, + ), + 'stepper_vertical_incomplete', + ); + + goldenTest( + goldenFile, + const ZetaStepper( + type: ZetaStepperType.vertical, + steps: [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2')), + ZetaStep(title: Text('Title 3')), + ], + currentStep: 4, + ), + 'stepper_vertical_complete', + ); + goldenTest( + goldenFile, + const ZetaStepper( + type: ZetaStepperType.vertical, + steps: [ + ZetaStep(title: Text('Title 1')), + ZetaStep(title: Text('Title 2'), disabled: true), + ZetaStep(title: Text('Title 3')), + ], + currentStep: 0, + ), + 'stepper_vertical_step_disabled', + ); + }); + + group('Performance Tests', () {}); +}