diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index fbfd738a..04b742f7 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -1,32 +1,30 @@ name: CI - Pull Request on: pull_request_target: - + # Pull Request Runs on the same branch will be cancelled concurrency: group: ${{ github.head_ref }} cancel-in-progress: true - jobs: code-quality: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - repository: ${{github.event.pull_request.head.repo.full_name}} - ref: ${{ github.head_ref }} - fetch-depth: 0 - - uses: subosito/flutter-action@v2 - with: - cache: true - - run: dart run build_runner build --delete-conflicting-outputs - - uses: ZebraDevs/flutter-code-quality@fix/testFailedComment - with: - token: ${{secrets.GITHUB_TOKEN}} - coverage-pass-score: '90' - - + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: ${{github.event.pull_request.head.repo.full_name}} + ref: ${{ github.head_ref }} + fetch-depth: 0 + - uses: subosito/flutter-action@v2 + with: + cache: true + - run: dart run build_runner build --delete-conflicting-outputs + - uses: ZebraDevs/flutter-code-quality@main + with: + token: ${{secrets.GITHUB_TOKEN}} + coverage-pass-score: "80" + check-secret: runs-on: ubuntu-latest outputs: @@ -41,7 +39,7 @@ jobs: fi deploy-preview: - name: Deploy preview version of the storybook on firebase + name: Deploy preview version of the widgetbook on firebase needs: [code-quality, check-secret] if: needs.check-secret.outputs.secret-exists == 'true' runs-on: ubuntu-latest diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2557a107..b4e9013b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.15.2" + ".": "0.16.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b0caab5..347e0d94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,116 @@ # Changelog +## [0.16.0](https://github.com/ZebraDevs/zeta_flutter/compare/v0.15.2...v0.16.0) (2024-10-22) + + +### ✨ New Features + +* added swipe-able actions to notification list item ([057defd](https://github.com/ZebraDevs/zeta_flutter/commit/057defd5a945382826ae8746416473943304da32)) +* comms buttons ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* updated notification list item to match latest design ([057defd](https://github.com/ZebraDevs/zeta_flutter/commit/057defd5a945382826ae8746416473943304da32)) +* **UX-1064:** Comms Buttons ([#182](https://github.com/ZebraDevs/zeta_flutter/issues/182)) ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* **UX-1073:** List Item notification ([#172](https://github.com/ZebraDevs/zeta_flutter/issues/172)) ([057defd](https://github.com/ZebraDevs/zeta_flutter/commit/057defd5a945382826ae8746416473943304da32)) + + +### 🪲 Bug Fixes + +* _getSlidableExtend() now won't return over 1.0 ([057defd](https://github.com/ZebraDevs/zeta_flutter/commit/057defd5a945382826ae8746416473943304da32)) +* A variety of small bug fixes ([#168](https://github.com/ZebraDevs/zeta_flutter/issues/168)) ([cb37705](https://github.com/ZebraDevs/zeta_flutter/commit/cb3770520d21b6e1de4f0bb928d800b12fc49042)) +* Add PlatformIs to not use dart:io ([cb37705](https://github.com/ZebraDevs/zeta_flutter/commit/cb3770520d21b6e1de4f0bb928d800b12fc49042)) +* added different constructors to zetacommsbutton ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* added golden group to testing_conventions.mdx ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* added some commas ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* added styles to banner text ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* Border sizes ([cb37705](https://github.com/ZebraDevs/zeta_flutter/commit/cb3770520d21b6e1de4f0bb928d800b12fc49042)) +* changed avatar xs so it shows two initals ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* changed Colors.transparent to Zeta.of(context).colors.surfaceDefault ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* changed example back ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* changed storybook to widgetbook in name of deploy preview in pull request github action ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* chat item ([#174](https://github.com/ZebraDevs/zeta_flutter/issues/174)) ([3ac64a7](https://github.com/ZebraDevs/zeta_flutter/commit/3ac64a7fa68de77d865c0f95745fdbac760d0984)) +* chat item actions _getSlidableExtend now won't return over 1.0 ([3ac64a7](https://github.com/ZebraDevs/zeta_flutter/commit/3ac64a7fa68de77d865c0f95745fdbac760d0984)) +* Components using the internal text input are now sized correctly ([38b865b](https://github.com/ZebraDevs/zeta_flutter/commit/38b865b8460fe0b8543bab3fa8245750ac2d3d6d)) +* get initials now returns the first and last name initials rather than first and second names. ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* moved analyzer package from dependencies to dev dependencies ([5987c21](https://github.com/ZebraDevs/zeta_flutter/commit/5987c211f79ff6357a47b462a654ca191f64498b)) +* put dev dependencies in alphabetical order ([5987c21](https://github.com/ZebraDevs/zeta_flutter/commit/5987c211f79ff6357a47b462a654ca191f64498b)) +* removed '$componentName' from all test files ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* removed analyzer from dependencies ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* set toggle related properties on non-toggle constructors to null ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* Spacings in example app ([cb37705](https://github.com/ZebraDevs/zeta_flutter/commit/cb3770520d21b6e1de4f0bb928d800b12fc49042)) +* **UX-1105:** Updated search bar to use internal text field ([#186](https://github.com/ZebraDevs/zeta_flutter/issues/186)) ([38b865b](https://github.com/ZebraDevs/zeta_flutter/commit/38b865b8460fe0b8543bab3fa8245750ac2d3d6d)) +* **UX-1141:** Update Spacing tokens ([#159](https://github.com/ZebraDevs/zeta_flutter/issues/159)) ([d22898f](https://github.com/ZebraDevs/zeta_flutter/commit/d22898f7e77704b5a4dd628320662530f1c2a1b4)) +* **UX-1207:** The search box on the search app bar now gets closed when the back button is pressed. ([58fc7f5](https://github.com/ZebraDevs/zeta_flutter/commit/58fc7f5c3e40888ade19a30a5592e70f5340585a)) +* **UX-1241:** Fixed inkwell on ZetaStepper ([#190](https://github.com/ZebraDevs/zeta_flutter/issues/190)) ([60a137f](https://github.com/ZebraDevs/zeta_flutter/commit/60a137f17dbeb989cf2a9f0b0dc3ee4b78ebb488)) +* **UX-1242:** Fixed extended app bar alignment ([#189](https://github.com/ZebraDevs/zeta_flutter/issues/189)) ([58fc7f5](https://github.com/ZebraDevs/zeta_flutter/commit/58fc7f5c3e40888ade19a30a5592e70f5340585a)) +* **UX-922:** FAB-redesign ([#181](https://github.com/ZebraDevs/zeta_flutter/issues/181)) ([11f266b](https://github.com/ZebraDevs/zeta_flutter/commit/11f266bbd955df2ef993f7edee2989bc9be60655)) + + +### 📈 Documentation + +* abstracted functions in test_counter ([5987c21](https://github.com/ZebraDevs/zeta_flutter/commit/5987c211f79ff6357a47b462a654ca191f64498b)) +* added brief docs for fontSize function in avatar ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* added comments to the class about named constructors ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* added doc comments to test_counter script and utils file ([5987c21](https://github.com/ZebraDevs/zeta_flutter/commit/5987c211f79ff6357a47b462a654ca191f64498b)) +* added figma and widgetbook links to all components ([#180](https://github.com/ZebraDevs/zeta_flutter/issues/180)) ([e1d2ba0](https://github.com/ZebraDevs/zeta_flutter/commit/e1d2ba04bfae99f7f383566b483c6558d9a91ec5)) +* added helper function section to TESTING_README ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* added info about how to run the script to testing read me ([5987c21](https://github.com/ZebraDevs/zeta_flutter/commit/5987c211f79ff6357a47b462a654ca191f64498b)) +* added link to excel sheet in TESTING_README ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* added testing_conventions.mdx to keep track how we are testing in flutter ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* changed comms button example to use assorted constructors ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* created test counter script ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* moved helper functions from test_counter to utils file ([5987c21](https://github.com/ZebraDevs/zeta_flutter/commit/5987c211f79ff6357a47b462a654ca191f64498b)) +* set initial values for comms button on widgetbook ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* test counter ([#187](https://github.com/ZebraDevs/zeta_flutter/issues/187)) ([5987c21](https://github.com/ZebraDevs/zeta_flutter/commit/5987c211f79ff6357a47b462a654ca191f64498b)) +* test_counter script improvements ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* testing counter script ([5987c21](https://github.com/ZebraDevs/zeta_flutter/commit/5987c211f79ff6357a47b462a654ca191f64498b)) + + +### ⛓️ Dependencies + +* **automated:** Update icons ([#191](https://github.com/ZebraDevs/zeta_flutter/issues/191)) ([0c09633](https://github.com/ZebraDevs/zeta_flutter/commit/0c09633b330594cb66457604ff684ab082ab2a4b)) +* **automated:** Update icons 2024-10-14 ([18ea9a2](https://github.com/ZebraDevs/zeta_flutter/commit/18ea9a2b123182f6da3382b9ee042297a4d721ae)) +* **automated:** Update tokens - 2024-08-27 ([#163](https://github.com/ZebraDevs/zeta_flutter/issues/163)) ([b499e22](https://github.com/ZebraDevs/zeta_flutter/commit/b499e221c58327ac6e6d18851986d3a503464de6)) +* **automated:** Update tokens ([#170](https://github.com/ZebraDevs/zeta_flutter/issues/170)) ([361b1c7](https://github.com/ZebraDevs/zeta_flutter/commit/361b1c7b7a271a990994ce35ac5d800315e7a753)) +* **automated:** Update tokens ([#176](https://github.com/ZebraDevs/zeta_flutter/issues/176)) ([2bd38de](https://github.com/ZebraDevs/zeta_flutter/commit/2bd38de3454f7544c0e19f9b60f1c2ce9e8e310c)) +* Update widgetbook dep for text scale addon ([#193](https://github.com/ZebraDevs/zeta_flutter/issues/193)) ([1852502](https://github.com/ZebraDevs/zeta_flutter/commit/185250206e151ce46e8311fd752bdeb23c82b6b6)) + + +### 🧪 Tests + +* Add new GoldenFiles class to simplify generating golden file Uris ([a77211a](https://github.com/ZebraDevs/zeta_flutter/commit/a77211a9fd33dfee170605a33098e9df6d634be2)) +* added background color test ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* added debugFillProperties test helper function ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* added loop for zetaavatar with border color for $size ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* added test count script ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* avatar ([#183](https://github.com/ZebraDevs/zeta_flutter/issues/183)) ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* banner ([#184](https://github.com/ZebraDevs/zeta_flutter/issues/184)) ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* banner tests ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* changed extension test to reflect changes ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* changed the iconbutton to a ZetaIcon for golden tests ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* comms goldens ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* Fix bug in golden tests that meant they failed on windows ([#177](https://github.com/ZebraDevs/zeta_flutter/issues/177)) ([a77211a](https://github.com/ZebraDevs/zeta_flutter/commit/a77211a9fd33dfee170605a33098e9df6d634be2)) +* implemented helper function in avatar and banner tests ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* improved banner tests ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* moved script files to different PR ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* organizing tests into groups ([#188](https://github.com/ZebraDevs/zeta_flutter/issues/188)) ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* removed comment that was cause github action to fail ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* removed unused import ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* replaced IconButton with Icon from golden tests ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* standardizing tests for badges ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* started making banner tests ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* Wrote tests for ZetaStepper ([60a137f](https://github.com/ZebraDevs/zeta_flutter/commit/60a137f17dbeb989cf2a9f0b0dc3ee4b78ebb488)) +* Wrote tests for ZetaTopAppbar ([58fc7f5](https://github.com/ZebraDevs/zeta_flutter/commit/58fc7f5c3e40888ade19a30a5592e70f5340585a)) + + +### 🧹 Miscellaneous Chores + +* **automated:** Lint commit and format ([ba4901d](https://github.com/ZebraDevs/zeta_flutter/commit/ba4901d74fff20d6319050f331c5f6d60f99b438)) +* **automated:** Lint commit and format ([ef050af](https://github.com/ZebraDevs/zeta_flutter/commit/ef050af9af8caf9552aa2b7aa1a4465320e4c870)) +* **automated:** Lint commit and format ([7fba9b2](https://github.com/ZebraDevs/zeta_flutter/commit/7fba9b27e399fc3ac5ebc0c09908ae4c3be92e3c)) +* **automated:** Lint commit and format ([af95815](https://github.com/ZebraDevs/zeta_flutter/commit/af958159b3e4f3fad06c24e64983aff5861a9482)) +* **automated:** Lint commit and format ([3ac64a7](https://github.com/ZebraDevs/zeta_flutter/commit/3ac64a7fa68de77d865c0f95745fdbac760d0984)) +* Merged goldenTest and goldenTestWithCallbacks. Also made the widgetType parameter optional and defaulted it to the type of widget. ([58fc7f5](https://github.com/ZebraDevs/zeta_flutter/commit/58fc7f5c3e40888ade19a30a5592e70f5340585a)) +* Update third party licenses ([cb37705](https://github.com/ZebraDevs/zeta_flutter/commit/cb3770520d21b6e1de4f0bb928d800b12fc49042)) + ## [0.15.2](https://github.com/ZebraDevs/zeta_flutter/compare/v0.15.1...v0.15.2) (2024-08-23) diff --git a/example/lib/home.dart b/example/lib/home.dart index 783f36ec..db398437 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:zeta_example/pages/components/accordion_example.dart'; +import 'package:zeta_example/pages/components/avatar_rail_example.dart'; import 'package:zeta_example/pages/components/avatar_example.dart'; import 'package:zeta_example/pages/components/badges_example.dart'; import 'package:zeta_example/pages/components/banner_example.dart'; @@ -61,6 +62,7 @@ class Component { final List components = [ Component(AccordionExample.name, (context) => const AccordionExample()), Component(TopAppBarExample.name, (context) => const TopAppBarExample()), + Component(AvatarRailExample.name, (context) => const AvatarRailExample()), Component(AvatarExample.name, (context) => const AvatarExample()), Component(BannerExample.name, (context) => const BannerExample()), Component(BadgesExample.name, (context) => const BadgesExample()), @@ -158,7 +160,7 @@ class _HomeState extends State { final _theme = theme..sort((a, b) => a.name.compareTo(b.name)); return ExampleScaffold( // x-release-please-start-version - name: 'zeta_flutter v0.15.2', + name: 'zeta_flutter v0.16.0', // x-release-please-end child: SingleChildScrollView( child: Column( diff --git a/example/lib/pages/components/avatar_rail_example.dart b/example/lib/pages/components/avatar_rail_example.dart new file mode 100644 index 00000000..3199a6bb --- /dev/null +++ b/example/lib/pages/components/avatar_rail_example.dart @@ -0,0 +1,96 @@ +// import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class AvatarRailExample extends StatefulWidget { + static const String name = 'AvatarRail'; + + const AvatarRailExample({super.key}); + + @override + State createState() => _AvatarRailExampleState(); +} + +class _AvatarRailExampleState extends State { + int? selected; + @override + Widget build(BuildContext context) { + final avatarList = [ + ZetaAvatar.initials( + initials: 'AZ', + label: 'Archie', + ), + ZetaAvatar.initials( + initials: 'BY', + label: 'Beth', + ), + ZetaAvatar.initials( + initials: 'CX', + label: 'Clara', + ), + ZetaAvatar.initials( + initials: 'DW', + label: 'Dan', + ), + ZetaAvatar.initials( + initials: 'EV', + label: 'Emily', + ), + ZetaAvatar.initials( + initials: 'FU', + label: 'Frank', + ), + ZetaAvatar.initials( + initials: 'GT', + label: 'George', + ), + ZetaAvatar.initials( + initials: 'HS', + label: 'Harith', + ), + ZetaAvatar.initials( + initials: 'IR', + label: 'Irene', + ), + ZetaAvatar.initials( + initials: 'KQ', + label: 'Katie', + ), + ]; + return ExampleScaffold( + name: AvatarRailExample.name, + child: SingleChildScrollView( + child: Column( + children: [ + for (final size in ZetaAvatarSize.values) + Row( + children: [ + Text(size.toString()), + SizedBox( + width: 500, + child: ZetaAvatarRail( + gap: 10, + size: size, + labelMaxLines: 3, + onTap: (key) => { + setState(() { + selected = int.parse(key.toString().replaceAll(RegExp(r'[^0-9]'), '')); + }) + }, + avatars: avatarList, + ), + ), + if (selected != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: avatarList[selected!].copyWith(size: size), + ), + ].gap(50), + ) + ], + ), + ), + ); + } +} 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/lib/pages/components/top_app_bar_example.dart b/example/lib/pages/components/top_app_bar_example.dart index 7e6cea1b..306703e1 100644 --- a/example/lib/pages/components/top_app_bar_example.dart +++ b/example/lib/pages/components/top_app_bar_example.dart @@ -15,20 +15,7 @@ class TopAppBarExample extends StatefulWidget { } class _TopAppBarExampleState extends State { - final _searchControllerExtended = ZetaSearchController(); - final _searchControllerRegular = ZetaSearchController(); - - void _showHideSearchExtended() { - _searchControllerExtended.isEnabled - ? _searchControllerExtended.closeSearch() - : _searchControllerExtended.startSearch(); - } - - void _showHideSearchRegular() { - _searchControllerRegular.isEnabled - ? _searchControllerRegular.closeSearch() - : _searchControllerRegular.startSearch(); - } + final _searchController = ZetaSearchController(); @override Widget build(BuildContext context) { @@ -78,8 +65,7 @@ class _TopAppBarExampleState extends State { ], ), Text('Centered', style: ZetaTextStyles.titleLarge), - ZetaTopAppBar( - type: ZetaTopAppBarType.centeredTitle, + ZetaTopAppBar.centered( leading: IconButton( onPressed: () {}, icon: ZetaIcon(Icons.menu), @@ -119,24 +105,20 @@ class _TopAppBarExampleState extends State { ], ), Text('Search', style: ZetaTextStyles.titleLarge), - ZetaTopAppBar( - type: ZetaTopAppBarType.centeredTitle, - leading: BackButton(), + ZetaTopAppBar.search( + leading: IconButton( + onPressed: () {}, + icon: ZetaIcon(Icons.menu), + ), title: Text("Title"), - actions: [ - IconButton( - onPressed: _showHideSearchRegular, - icon: ZetaIcon(ZetaIcons.search), - ) - ], - searchController: _searchControllerRegular, + searchController: _searchController, onSearch: (text) => debugPrint('search text: $text'), onSearchMicrophoneIconPressed: () async { var sampleTexts = ['This is a sample text', 'Another sample', 'Speech recognition text', 'Example']; var generatedText = sampleTexts[Random().nextInt(sampleTexts.length)]; - _searchControllerRegular.text = generatedText; + _searchController.text = generatedText; }, ), Text('Extended', style: ZetaTextStyles.titleLarge), @@ -188,48 +170,6 @@ class _TopAppBarExampleState extends State { ], ), ), - Text('Extended Search', style: ZetaTextStyles.titleLarge), - SizedBox( - width: 800, - height: 200, - child: CustomScrollView( - slivers: [ - ZetaTopAppBar.extended( - leading: BackButton(), - title: Text("Title"), - actions: [ - IconButton( - onPressed: _showHideSearchExtended, - icon: ZetaIcon(ZetaIcons.search), - ) - ], - searchController: _searchControllerExtended, - onSearch: (text) => debugPrint('search text: $text'), - onSearchMicrophoneIconPressed: () async { - var sampleTexts = [ - 'This is a sample text', - 'Another sample', - 'Speech recognition text', - 'Example' - ]; - var generatedText = sampleTexts[Random().nextInt(sampleTexts.length)]; - _searchControllerExtended.text = generatedText; - }, - ), - SliverToBoxAdapter( - child: Container( - width: 800, - height: 800, - color: Zeta.of(context).colors.surfaceSelectedHover, - child: CustomPaint( - painter: Painter(zeta: Zeta.of(context)), - size: Size(800, 800), - ), - ), - ), - ], - ), - ), ].gap(20), ), ), diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 4d2c877c..fbd492e5 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -6,7 +6,7 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - url_launcher_macos (0.0.1): @@ -16,7 +16,7 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) + - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: @@ -26,8 +26,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin shared_preferences_foundation: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin - sqflite: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin + sqflite_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos @@ -35,8 +35,8 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 diff --git a/example/widgetbook/main.dart b/example/widgetbook/main.dart index 5397a190..6a5202c6 100644 --- a/example/widgetbook/main.dart +++ b/example/widgetbook/main.dart @@ -11,6 +11,7 @@ import 'pages/components/notification_list_item_widgetbook.dart'; import 'pages/components/slider_widgetbook.dart'; import 'pages/components/text_input_widgetbook.dart'; import 'pages/components/top_app_bar_widgetbook.dart'; +import 'pages/components/avatar_rail_widgetbook.dart'; import 'pages/components/avatar_widgetbook.dart'; import 'pages/components/badges_widgetbook.dart'; import 'pages/components/banner_widgetbook.dart'; @@ -121,6 +122,13 @@ class _HotReloadState extends State { name: 'Components', isInitiallyExpanded: false, children: [ + WidgetbookComponent( + name: 'Avatar', + useCases: [ + WidgetbookUseCase(name: 'Avatar', builder: (context) => avatarUseCase(context)), + WidgetbookUseCase(name: 'Avatar Rail', builder: (context) => avatarRailUseCase(context)), + ], + ), WidgetbookComponent( name: 'Top App Bar', useCases: [ @@ -181,7 +189,6 @@ class _HotReloadState extends State { ], ), WidgetbookUseCase(name: 'Accordion', builder: (context) => accordionUseCase(context)), - WidgetbookUseCase(name: 'Avatar', builder: (context) => avatarUseCase(context)), WidgetbookUseCase(name: 'Banners', builder: (context) => bannerUseCase(context)), WidgetbookUseCase(name: 'Bottom Sheet', builder: (context) => bottomSheetContentUseCase(context)), WidgetbookUseCase(name: 'BreadCrumbs', builder: (context) => breadCrumbsUseCase(context)), @@ -246,7 +253,7 @@ class _HotReloadState extends State { ZetaAddon(), InspectorAddon(enabled: false), ZoomAddon(initialZoom: 1.0), - TextScaleAddon(scales: [1.0, 1.2, 1.4, 1.6, 1.8, 2.0], initialScale: 1), + TextScaleAddon(min: 1, max: 2, divisions: 4, initialScale: 1), ], ); } diff --git a/example/widgetbook/pages/components/avatar_rail_widgetbook.dart b/example/widgetbook/pages/components/avatar_rail_widgetbook.dart new file mode 100644 index 00000000..a70122be --- /dev/null +++ b/example/widgetbook/pages/components/avatar_rail_widgetbook.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../utils/scaffold.dart'; + +Widget avatarRailUseCase(BuildContext context) { + final Widget image = Image.asset('assets/Omer.jpg', fit: BoxFit.cover); + final colors = Zeta.of(context).colors; + + return WidgetbookScaffold( + builder: (context, _) => ZetaAvatarRail( + labelMaxLines: context.knobs.int.slider(label: 'Label Max Lines', min: 1, max: 3, initialValue: 1), + avatars: [ + ZetaAvatar( + image: context.knobs.boolean(label: 'Image') ? image : null, + size: context.knobs.list( + label: 'Size', + options: ZetaAvatarSize.values, + labelBuilder: (value) => value.name.split('.').last.toUpperCase(), + initialOption: ZetaAvatarSize.m, + ), + upperBadge: context.knobs.boolean(label: 'Status Badge', initialValue: false) + ? ZetaAvatarBadge.icon( + icon: ZetaIcons.close, + color: context.knobs.colorOrNull(label: "Upper Badge Color", initialValue: colors.primitives.green) ?? + colors.mainDefault, + ) + : null, + borderColor: context.knobs.colorOrNull(label: 'Outline', initialValue: colors.primitives.green), + lowerBadge: context.knobs.boolean(label: 'Notification Badge', initialValue: false) + ? ZetaAvatarBadge.notification( + value: context.knobs.intOrNull.input(label: "Value", initialValue: 1), + ) + : null, + initials: context.knobs.stringOrNull(label: 'Initials', initialValue: 'AZ'), + backgroundColor: + context.knobs.colorOrNull(label: 'Background color', initialValue: colors.primitives.purple.shade80), + onTap: () => print('Avatar tapped'), + label: context.knobs.stringOrNull(label: 'Label', initialValue: 'ABC'), + ), + ZetaAvatar( + image: context.knobs.boolean(label: 'Image') ? image : null, + size: context.knobs.list( + label: 'Size', + options: ZetaAvatarSize.values, + labelBuilder: (value) => value.name.split('.').last.toUpperCase(), + initialOption: ZetaAvatarSize.m, + ), + upperBadge: context.knobs.boolean(label: 'Status Badge', initialValue: false) + ? ZetaAvatarBadge.icon( + icon: ZetaIcons.close, + color: context.knobs.colorOrNull(label: "Upper Badge Color", initialValue: colors.primitives.green) ?? + colors.mainDefault, + ) + : null, + borderColor: context.knobs.colorOrNull(label: 'Outline', initialValue: colors.primitives.green), + lowerBadge: context.knobs.boolean(label: 'Notification Badge', initialValue: false) + ? ZetaAvatarBadge.notification( + value: context.knobs.intOrNull.input(label: "Value", initialValue: 1), + ) + : null, + initials: context.knobs.stringOrNull(label: 'Initials', initialValue: 'AZ'), + backgroundColor: + context.knobs.colorOrNull(label: 'Background color', initialValue: colors.primitives.purple.shade80), + onTap: () => print('Avatar tapped'), + label: context.knobs.stringOrNull(label: 'Label', initialValue: 'ABC'), + ), + ZetaAvatar( + image: context.knobs.boolean(label: 'Image') ? image : null, + size: context.knobs.list( + label: 'Size', + options: ZetaAvatarSize.values, + labelBuilder: (value) => value.name.split('.').last.toUpperCase(), + initialOption: ZetaAvatarSize.m, + ), + upperBadge: context.knobs.boolean(label: 'Status Badge', initialValue: false) + ? ZetaAvatarBadge.icon( + icon: ZetaIcons.close, + color: context.knobs.colorOrNull(label: "Upper Badge Color", initialValue: colors.primitives.green) ?? + colors.mainDefault, + ) + : null, + borderColor: context.knobs.colorOrNull(label: 'Outline', initialValue: colors.primitives.green), + lowerBadge: context.knobs.boolean(label: 'Notification Badge', initialValue: false) + ? ZetaAvatarBadge.notification( + value: context.knobs.intOrNull.input(label: "Value", initialValue: 1), + ) + : null, + initials: context.knobs.stringOrNull(label: 'Initials', initialValue: 'AZ'), + backgroundColor: + context.knobs.colorOrNull(label: 'Background color', initialValue: colors.primitives.purple.shade80), + onTap: () => print('Avatar tapped'), + label: context.knobs.stringOrNull(label: 'Label', initialValue: 'ABC'), + ), + ZetaAvatar( + image: context.knobs.boolean(label: 'Image') ? image : null, + size: context.knobs.list( + label: 'Size', + options: ZetaAvatarSize.values, + labelBuilder: (value) => value.name.split('.').last.toUpperCase(), + initialOption: ZetaAvatarSize.m, + ), + upperBadge: context.knobs.boolean(label: 'Status Badge', initialValue: false) + ? ZetaAvatarBadge.icon( + icon: ZetaIcons.close, + color: context.knobs.colorOrNull(label: "Upper Badge Color", initialValue: colors.primitives.green) ?? + colors.mainDefault, + ) + : null, + borderColor: context.knobs.colorOrNull(label: 'Outline', initialValue: colors.primitives.green), + lowerBadge: context.knobs.boolean(label: 'Notification Badge', initialValue: false) + ? ZetaAvatarBadge.notification( + value: context.knobs.intOrNull.input(label: "Value", initialValue: 1), + ) + : null, + initials: context.knobs.stringOrNull(label: 'Initials', initialValue: 'AZ'), + backgroundColor: + context.knobs.colorOrNull(label: 'Background color', initialValue: colors.primitives.purple.shade80), + onTap: () => print('Avatar tapped'), + label: context.knobs.stringOrNull(label: 'Label', initialValue: 'ABC'), + ), + ZetaAvatar( + image: context.knobs.boolean(label: 'Image') ? image : null, + size: context.knobs.list( + label: 'Size', + options: ZetaAvatarSize.values, + labelBuilder: (value) => value.name.split('.').last.toUpperCase(), + initialOption: ZetaAvatarSize.m, + ), + upperBadge: context.knobs.boolean(label: 'Status Badge', initialValue: false) + ? ZetaAvatarBadge.icon( + icon: ZetaIcons.close, + color: context.knobs.colorOrNull(label: "Upper Badge Color", initialValue: colors.primitives.green) ?? + colors.mainDefault, + ) + : null, + borderColor: context.knobs.colorOrNull(label: 'Outline', initialValue: colors.primitives.green), + lowerBadge: context.knobs.boolean(label: 'Notification Badge', initialValue: false) + ? ZetaAvatarBadge.notification( + value: context.knobs.intOrNull.input(label: "Value", initialValue: 1), + ) + : null, + initials: context.knobs.stringOrNull(label: 'Initials', initialValue: 'AZ'), + backgroundColor: + context.knobs.colorOrNull(label: 'Background color', initialValue: colors.primitives.purple.shade80), + onTap: () => print('Avatar tapped'), + label: context.knobs.stringOrNull(label: 'Label', initialValue: 'ABC'), + ), + ZetaAvatar( + image: context.knobs.boolean(label: 'Image') ? image : null, + size: context.knobs.list( + label: 'Size', + options: ZetaAvatarSize.values, + labelBuilder: (value) => value.name.split('.').last.toUpperCase(), + initialOption: ZetaAvatarSize.m, + ), + upperBadge: context.knobs.boolean(label: 'Status Badge', initialValue: false) + ? ZetaAvatarBadge.icon( + icon: ZetaIcons.close, + color: context.knobs.colorOrNull(label: "Upper Badge Color", initialValue: colors.primitives.green) ?? + colors.mainDefault, + ) + : null, + borderColor: context.knobs.colorOrNull(label: 'Outline', initialValue: colors.primitives.green), + lowerBadge: context.knobs.boolean(label: 'Notification Badge', initialValue: false) + ? ZetaAvatarBadge.notification( + value: context.knobs.intOrNull.input(label: "Value", initialValue: 1), + ) + : null, + initials: context.knobs.stringOrNull(label: 'Initials', initialValue: 'AZ'), + backgroundColor: + context.knobs.colorOrNull(label: 'Background color', initialValue: colors.primitives.purple.shade80), + onTap: () => print('Avatar tapped'), + label: context.knobs.stringOrNull(label: 'Label', initialValue: 'ABC'), + ), + ], + ), + ); +} diff --git a/example/widgetbook/pages/components/avatar_widgetbook.dart b/example/widgetbook/pages/components/avatar_widgetbook.dart index 2b37aa90..4bab90c1 100644 --- a/example/widgetbook/pages/components/avatar_widgetbook.dart +++ b/example/widgetbook/pages/components/avatar_widgetbook.dart @@ -33,6 +33,9 @@ Widget avatarUseCase(BuildContext context) { initials: context.knobs.stringOrNull(label: 'Initials', initialValue: 'AZ'), backgroundColor: context.knobs.colorOrNull(label: 'Background color', initialValue: colors.primitives.purple.shade80), + onTap: () => print('Avatar tapped'), + label: context.knobs.stringOrNull(label: 'Label', initialValue: 'ABC'), + labelMaxLines: context.knobs.int.slider(label: 'Label Max Lines', min: 1, max: 3, initialValue: 1), ), ); } 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/example/widgetbook/pages/components/top_app_bar_widgetbook.dart b/example/widgetbook/pages/components/top_app_bar_widgetbook.dart index 6baedba8..ecc9b378 100644 --- a/example/widgetbook/pages/components/top_app_bar_widgetbook.dart +++ b/example/widgetbook/pages/components/top_app_bar_widgetbook.dart @@ -13,7 +13,7 @@ Widget defaultTopAppBarUseCase(BuildContext context) { label: "Title Alignment", options: [ ZetaTopAppBarType.defaultAppBar, - ZetaTopAppBarType.centeredTitle, + ZetaTopAppBarType.centered, ], initialOption: ZetaTopAppBarType.defaultAppBar, labelBuilder: (option) { @@ -53,7 +53,7 @@ Widget defaultTopAppBarUseCase(BuildContext context) { icon: ZetaIcon(ZetaIcons.more_vertical), ) ] - : null, + : [], ), ], )); @@ -84,7 +84,7 @@ class _SearchUseCaseState extends State<_SearchUseCase> { label: "Title Alignment", options: [ ZetaTopAppBarType.defaultAppBar, - ZetaTopAppBarType.centeredTitle, + ZetaTopAppBarType.centered, ], initialOption: ZetaTopAppBarType.defaultAppBar, labelBuilder: (option) { @@ -101,7 +101,7 @@ class _SearchUseCaseState extends State<_SearchUseCase> { initialValue: false, ); - return ZetaTopAppBar( + return ZetaTopAppBar.search( leading: IconButton( onPressed: () {}, icon: ZetaIcon(leadingIcon), @@ -118,43 +118,26 @@ class _SearchUseCaseState extends State<_SearchUseCase> { searchController.text = generatedText; } : null, - actions: [ - IconButton( - onPressed: () { - searchController.isEnabled ? searchController.closeSearch() : searchController.startSearch(); - }, - icon: ZetaIcon(ZetaIcons.search)), - ], ); } } -Widget extendedTopAppBarUseCase(BuildContext context) => ExtendedSearch(); +Widget extendedTopAppBarUseCase(BuildContext context) => ExtendedTopAppBar(); -class ExtendedSearch extends StatefulWidget { - const ExtendedSearch({super.key}); +class ExtendedTopAppBar extends StatefulWidget { + const ExtendedTopAppBar({super.key}); @override - State createState() => _ExtendedSearchState(); + State createState() => _ExtendedTopAppBarState(); } -class _ExtendedSearchState extends State { - final _searchControllerExtended = ZetaSearchController(); - - void _showHideSearchExtended() { - _searchControllerExtended.isEnabled - ? _searchControllerExtended.closeSearch() - : _searchControllerExtended.startSearch(); - } - +class _ExtendedTopAppBarState extends State { @override Widget build(BuildContext context) { final title = context.knobs.string(label: "Title", initialValue: "Title"); final leadingIcon = iconKnob(context, name: 'Leading Icon', initial: ZetaIcons.hamburger_menu); - final showSearch = context.knobs.boolean(label: 'Search variant', initialValue: false); - return WidgetbookScaffold( removeBody: true, builder: (context, constraints) => SafeArea( @@ -166,41 +149,20 @@ class _ExtendedSearchState extends State { ZetaTopAppBar.extended( leading: IconButton(icon: ZetaIcon(leadingIcon), onPressed: () {}), title: Text(title), - actions: showSearch - ? [ - IconButton( - onPressed: _showHideSearchExtended, - icon: ZetaIcon(ZetaIcons.search), - ) - ] - : [ - IconButton( - onPressed: () {}, - icon: ZetaIcon(Icons.language), - ), - IconButton( - onPressed: () {}, - icon: ZetaIcon(Icons.favorite), - ), - IconButton( - onPressed: () {}, - icon: ZetaIcon(ZetaIcons.more_vertical), - ) - ], - searchController: showSearch ? _searchControllerExtended : null, - onSearch: showSearch ? (text) => debugPrint('search text: $text') : null, - onSearchMicrophoneIconPressed: showSearch - ? () async { - var sampleTexts = [ - 'This is a sample text', - 'Another sample', - 'Speech recognition text', - 'Example' - ]; - var generatedText = sampleTexts[Random().nextInt(sampleTexts.length)]; - _searchControllerExtended.text = generatedText; - } - : null, + actions: [ + IconButton( + onPressed: () {}, + icon: ZetaIcon(Icons.language), + ), + IconButton( + onPressed: () {}, + icon: ZetaIcon(Icons.favorite), + ), + IconButton( + onPressed: () {}, + icon: ZetaIcon(ZetaIcons.more_vertical), + ) + ], ), SliverToBoxAdapter( child: Container( @@ -232,7 +194,7 @@ class Painter extends CustomPainter { var p1 = Offset(i, -10); var p2 = Offset(constraints.maxHeight + i, constraints.maxHeight * 4); var paint = Paint() - ..color = Zeta.of(context).colors.mainPrimary + ..color = Zeta.of(context).colors.surfacePrimarySubtle ..strokeWidth = Zeta.of(context).spacing.minimum; canvas.drawLine(p1, p2, paint); } diff --git a/example/widgetbook/pages/introduction.dart b/example/widgetbook/pages/introduction.dart index fc05a5b2..10798bdc 100644 --- a/example/widgetbook/pages/introduction.dart +++ b/example/widgetbook/pages/introduction.dart @@ -71,7 +71,7 @@ class _IntroductionWidgetbookState extends State { Expanded( child: Text( // x-release-please-start-version - 'zeta_flutter v0.15.2', + 'zeta_flutter v0.16.0', // x-release-please-end style: ZetaTextStyles.displayLarge.copyWith(fontSize: largeScreen ? null : 24), ), diff --git a/lib/generated/icons/icons.g.dart b/lib/generated/icons/icons.g.dart index a75631a4..d040c869 100644 --- a/lib/generated/icons/icons.g.dart +++ b/lib/generated/icons/icons.g.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; /// Current version of Zeta Icons // x-release-please-start-version -const zetaIconsVersion = '0.6.2'; +const zetaIconsVersion = '0.8.0'; // x-release-please-end /// Zeta Icons. Full list of icons can be found at [Zeta Icons](https://design.zebra.com/icons/). diff --git a/lib/src/components/avatar_rail/avatar_rail.dart b/lib/src/components/avatar_rail/avatar_rail.dart new file mode 100644 index 00000000..c57feac4 --- /dev/null +++ b/lib/src/components/avatar_rail/avatar_rail.dart @@ -0,0 +1,146 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../utils/utils.dart'; +import '../avatars/avatar.dart'; + +/// A stateless widget that represents an avatar rail in the Zeta application. +/// +/// The `ZetaAvatarRail` widget is used to display a horizontal rail of avatars, +/// typically used for navigation or selection purposes within the application. +/// +/// This widget does not maintain any state and relies on its parent and children widgets to +/// provide the necessary data and handle interactions. +/// +/// Example usage: +/// +/// ```dart +/// ZetaAvatarRail( +/// avatars: [ +/// ZetaAvatar.initials( +/// key: Key('avatar1'), +/// initials: 'AZ', +/// onTap: () => print('Avatar tapped'), +/// label: 'Archie', +/// ), +/// ZetaAvatar.initials( +/// key: Key('avatar2'), +/// initials: 'BY', +/// onTap: () => print('Avatar tapped'), +/// label: 'Beth', +/// ), +/// ZetaAvatar.initials( +/// key: Key('avatar3'), +/// initials: 'CX', +/// onTap: () => print('Avatar tapped'), +/// label: 'Carla', +/// ), +/// ] +/// ) +/// ``` +/// +/// See also: +/// +/// * [StatelessWidget], which is the superclass of this widget. +/// * [ZetaAvatar], which is used within this rail to represent individual avatars. +/// {@category Components} +/// +/// Figma: https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=20816-388 +/// +/// Widgetbook: https://design.zebra.com/flutter/widgetbook/index.html#/?path=components/avatar/avatar-rail +class ZetaAvatarRail extends StatelessWidget { + /// + const ZetaAvatarRail({ + super.key, + this.size, + required this.avatars, + this.labelTextStyle, + this.labelMaxLines = 1, + this.onTap, + this.gap, + }); + + /// A list of `ZetaAvatar` objects representing the avatars to be displayed. + final List avatars; + + /// The size of the [ZetaAvatar]s + final ZetaAvatarSize? size; + + /// The text style to be applied to the label of the [ZetaAvatar]s. + final TextStyle? labelTextStyle; + + /// The maximum number of lines to be displayed in the label of the [ZetaAvatar]s. + final int labelMaxLines; + + /// A callback function to be executed when an [ZetaAvatar] is tapped. + /// The function receives the key of the tapped [ZetaAvatar] as a parameter. + /// If no key is provided, the index of the [ZetaAvatar] in the list is used. + final void Function(Key)? onTap; + + /// The gap between the avatars. + /// Defaults to 'Zeta.of(context).spacing.small) + final double? gap; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + hitTestBehavior: HitTestBehavior.translucent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final avatar in avatars) + avatar.copyWith( + size: size, + labelTextStyle: labelTextStyle, + labelMaxLines: labelMaxLines, + onTap: () => onTap?.call(key ?? Key(avatars.indexOf(avatar).toString())), + key: key ?? Key(avatars.indexOf(avatar).toString()), + ), + ].gap(gap ?? Zeta.of(context).spacing.small), + ), + ), + ], + ); + } + + /// Returns pixel size for [ZetaAvatarSize] + static double pixelSize(BuildContext context, ZetaAvatarSize size) { + switch (size) { + case ZetaAvatarSize.xxxl: + return Zeta.of(context).spacing.minimum * 50; // TODO(UX-1202): ZetaSpacingBase + // return ZetaSpacingBase.x50; + case ZetaAvatarSize.xxl: + return Zeta.of(context).spacing.minimum * 30; // TODO(UX-1202): ZetaSpacingBase + // return ZetaSpacingBase.x30; + case ZetaAvatarSize.xl: + return Zeta.of(context).spacing.xl_10; + case ZetaAvatarSize.l: + return Zeta.of(context).spacing.xl_9; + case ZetaAvatarSize.m: + return Zeta.of(context).spacing.xl_8; + case ZetaAvatarSize.s: + return Zeta.of(context).spacing.xl_6; + case ZetaAvatarSize.xs: + return Zeta.of(context).spacing.xl_5; + case ZetaAvatarSize.xxs: + return Zeta.of(context).spacing.xl_4; + case ZetaAvatarSize.xxxs: + return Zeta.of(context).spacing.xl_2; + } + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('labelTextStyle', labelTextStyle)) + ..add(EnumProperty('size', size)) + ..add(IntProperty('labelMaxLines', labelMaxLines)) + ..add(ObjectFlagProperty.has('onTap', onTap)) + ..add(DoubleProperty('gap', gap)); + } +} diff --git a/lib/src/components/avatars/avatar.dart b/lib/src/components/avatars/avatar.dart index d4ba76bf..b9e5c14f 100644 --- a/lib/src/components/avatars/avatar.dart +++ b/lib/src/components/avatars/avatar.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -38,7 +39,7 @@ enum ZetaAvatarSize { /// /// Figma: https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=20816-388 /// -/// Widgetbook: https://zeta-ds.web.app/flutter/widgetbook/index.html#/?path=components/avatar +/// Widgetbook: https://zeta-ds.web.app/flutter/widgetbook/index.html#/?path=components/avatar/avatar class ZetaAvatar extends ZetaStatelessWidget { /// Constructor for [ZetaAvatar] const ZetaAvatar({ @@ -54,6 +55,10 @@ class ZetaAvatar extends ZetaStatelessWidget { this.semanticUpperBadgeLabel = 'upperBadge', this.semanticLowerBadgeLabel = 'lowerBadge', this.initialTextStyle, + this.label, + this.labelTextStyle, + this.labelMaxLines = 1, + this.onTap, }); /// Constructor for [ZetaAvatar] with image. @@ -67,6 +72,10 @@ class ZetaAvatar extends ZetaStatelessWidget { this.semanticLabel = 'avatar', this.semanticUpperBadgeLabel = 'upperBadge', this.semanticLowerBadgeLabel = 'lowerBadge', + this.label, + this.labelTextStyle, + this.labelMaxLines = 1, + this.onTap, }) : backgroundColor = null, initials = null, initialTextStyle = null; @@ -84,6 +93,10 @@ class ZetaAvatar extends ZetaStatelessWidget { this.semanticUpperBadgeLabel = 'upperBadge', this.semanticLowerBadgeLabel = 'lowerBadge', this.initialTextStyle, + this.label, + this.labelTextStyle, + this.labelMaxLines = 1, + this.onTap, }) : image = null; /// Constructor for [ZetaAvatar] with initials from a full name. @@ -99,6 +112,10 @@ class ZetaAvatar extends ZetaStatelessWidget { this.semanticUpperBadgeLabel = 'upperBadge', this.semanticLowerBadgeLabel = 'lowerBadge', this.initialTextStyle, + this.label, + this.labelTextStyle, + this.labelMaxLines = 1, + this.onTap, }) : image = null, initials = name.initials; @@ -155,6 +172,18 @@ class ZetaAvatar extends ZetaStatelessWidget { /// ``` final TextStyle? initialTextStyle; + /// Label to display below the avatar. + final String? label; + + /// Text style for label. + final TextStyle? labelTextStyle; + + /// Maximum number of lines for label. + final int labelMaxLines; + + /// Callback when avatar is tapped. + final VoidCallback? onTap; + /// Return copy of avatar with certain changed fields ZetaAvatar copyWith({ ZetaAvatarSize? size, @@ -164,6 +193,11 @@ class ZetaAvatar extends ZetaStatelessWidget { Color? borderColor, ZetaAvatarBadge? lowerBadge, ZetaAvatarBadge? upperBadge, + String? label, + TextStyle? labelTextStyle, + int? labelMaxLines, + VoidCallback? onTap, + Key? key, }) { return ZetaAvatar( size: size ?? this.size, @@ -173,6 +207,11 @@ class ZetaAvatar extends ZetaStatelessWidget { borderColor: borderColor ?? this.borderColor, lowerBadge: lowerBadge ?? this.lowerBadge, upperBadge: upperBadge ?? this.upperBadge, + label: label ?? this.label, + labelTextStyle: labelTextStyle ?? this.labelTextStyle, + labelMaxLines: labelMaxLines ?? this.labelMaxLines, + onTap: onTap ?? this.onTap, + key: key ?? this.key, ); } @@ -212,58 +251,113 @@ class ZetaAvatar extends ZetaStatelessWidget { child: Semantics( value: semanticLabel, child: SelectionContainer.disabled( - child: Stack( + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Container( - width: pSize, - height: pSize, - decoration: BoxDecoration( - border: borderColor != null ? Border.all(color: borderColor!, width: 0) : null, - borderRadius: Zeta.of(context).radius.full, - color: backgroundColor ?? (_showPlaceholder ? zetaColors.surfacePrimary : zetaColors.surfaceWarm), - ), - child: borderColor != null - ? Container( - width: pSize, - height: pSize, - decoration: BoxDecoration( - color: backgroundColor ?? zetaColors.surfaceHover, - border: Border.all(color: borderColor!, width: borderSize(context)), - borderRadius: Zeta.of(context).radius.full, - ), - child: ClipRRect( - borderRadius: Zeta.of(context).radius.full, - child: innerContent, - ), - ) - : DecoratedBox( - decoration: BoxDecoration( - borderRadius: Zeta.of(context).radius.full, - color: backgroundColor ?? zetaColors.surfaceHover, - ), - child: ClipRRect( - borderRadius: Zeta.of(context).radius.full, - child: innerContent, - ), + // Container( TODO(Luke) check old version here + // width: pSize, + // height: pSize, + // decoration: BoxDecoration( + // border: borderColor != null ? Border.all(color: borderColor!, width: 0) : null, + // borderRadius: Zeta.of(context).radius.full, + // color: backgroundColor ?? (_showPlaceholder ? zetaColors.surfacePrimary : zetaColors.surfaceWarm), + // ), + // child: borderColor != null + // ? Container( + // width: pSize, + // height: pSize, + // decoration: BoxDecoration( + // color: backgroundColor ?? zetaColors.surfaceHover, + // border: Border.all(color: borderColor!, width: borderSize(context)), + // borderRadius: Zeta.of(context).radius.full, + // ), + // child: ClipRRect( + // borderRadius: Zeta.of(context).radius.full, + // child: innerContent, + // ), + // ) + // : DecoratedBox( + // decoration: BoxDecoration( + // borderRadius: Zeta.of(context).radius.full, + // color: backgroundColor ?? zetaColors.surfaceHover, + // ), + // child: ClipRRect( + // borderRadius: Zeta.of(context).radius.full, + // child: innerContent, + // ), + Stack( + children: [ + GestureDetector( + onTap: onTap, + child: Container( + width: pSize, + height: pSize, + decoration: BoxDecoration( + border: borderColor != null ? Border.all(color: borderColor!, width: 0) : null, + borderRadius: Zeta.of(context).radius.full, + color: backgroundColor ?? + (_showPlaceholder ? zetaColors.surfacePrimary : zetaColors.primitives.cool.shade20), ), - ), - if (upperBadge != null) - Positioned( - right: Zeta.of(context).spacing.none, - child: Semantics( - value: semanticLowerBadgeLabel, - child: upperBadge!.copyWith( - size: size, + child: borderColor != null + ? Container( + width: pSize, + height: pSize, + decoration: BoxDecoration( + color: backgroundColor ?? zetaColors.surfaceHover, + border: Border.all(color: borderColor!, width: borderSize(context)), + borderRadius: Zeta.of(context).radius.full, + ), + child: ClipRRect( + borderRadius: Zeta.of(context).radius.full, + child: innerContent, + ), + ) + : DecoratedBox( + decoration: BoxDecoration( + borderRadius: Zeta.of(context).radius.full, + color: backgroundColor ?? zetaColors.surfaceHover, + ), + child: ClipRRect( + borderRadius: Zeta.of(context).radius.full, + child: innerContent, + ), + ), ), ), + if (upperBadge != null) + Positioned( + right: Zeta.of(context).spacing.none, + child: Semantics( + value: semanticLowerBadgeLabel, + child: upperBadge!.copyWith( + size: size, + ), + ), + ), + if (lowerBadge != null) + Positioned( + right: Zeta.of(context).spacing.none, + bottom: Zeta.of(context).spacing.none, + child: Semantics( + value: semanticLowerBadgeLabel, + child: lowerBadge!.copyWith(size: size), + ), + ), + ], + ), + if (label != null) + SizedBox( + height: Zeta.of(context).spacing.minimum, ), - if (lowerBadge != null) - Positioned( - right: Zeta.of(context).spacing.none, - bottom: Zeta.of(context).spacing.none, - child: Semantics( - value: semanticLowerBadgeLabel, - child: lowerBadge!.copyWith(size: size), + if (label != null) + SizedBox( + width: pSize, + child: Text( + label!, + style: labelTextStyle ?? size.labelStyle(context).copyWith(color: zetaColors.mainSubtle), + maxLines: labelMaxLines, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, ), ), ], @@ -286,7 +380,11 @@ class ZetaAvatar extends ZetaStatelessWidget { ..add(StringProperty('semanticUpperBadgeValue', semanticUpperBadgeLabel)) ..add(StringProperty('semanticValue', semanticLabel)) ..add(StringProperty('semanticLowerBadgeValue', semanticLowerBadgeLabel)) - ..add(DiagnosticsProperty('initialTextStyle', initialTextStyle)); + ..add(DiagnosticsProperty('initialTextStyle', initialTextStyle)) + ..add(DiagnosticsProperty('labelTextStyle', labelTextStyle)) + ..add(StringProperty('label', label)) + ..add(IntProperty('labelMaxLines', labelMaxLines)) + ..add(ObjectFlagProperty.has('onTap', onTap)); } /// Returns pixel size for [ZetaAvatarSize] @@ -347,6 +445,26 @@ extension on ZetaAvatarSize { double fontSize(BuildContext context) { return ZetaAvatar.fontSize(context, this); } + + TextStyle labelStyle(BuildContext context) { + switch (this) { + case ZetaAvatarSize.xxxl: + return ZetaTextStyles.displaySmall; + case ZetaAvatarSize.xxl: + case ZetaAvatarSize.xl: + return ZetaTextStyles.bodyLarge; + case ZetaAvatarSize.l: + return ZetaTextStyles.bodyMedium; + case ZetaAvatarSize.m: + return ZetaTextStyles.bodySmall; + case ZetaAvatarSize.s: + case ZetaAvatarSize.xs: + case ZetaAvatarSize.xxs: + return ZetaTextStyles.bodyXSmall; + case ZetaAvatarSize.xxxs: + return ZetaTextStyles.bodyXSmall; + } + } } /// Enum of types for [ZetaAvatarBadge] diff --git a/lib/src/components/components.dart b/lib/src/components/components.dart index ab887229..63bae3a4 100644 --- a/lib/src/components/components.dart +++ b/lib/src/components/components.dart @@ -1,4 +1,5 @@ export 'accordion/accordion.dart'; +export 'avatar_rail/avatar_rail.dart'; export 'avatars/avatar.dart'; export 'badges/indicator.dart'; export 'badges/label.dart'; @@ -45,7 +46,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/screen_header_bar/screen_header_bar.dart b/lib/src/components/screen_header_bar/screen_header_bar.dart index 544eefe6..4212abf0 100644 --- a/lib/src/components/screen_header_bar/screen_header_bar.dart +++ b/lib/src/components/screen_header_bar/screen_header_bar.dart @@ -50,7 +50,7 @@ class ZetaScreenHeaderBar extends ZetaStatelessWidget { title: title, titleTextStyle: ZetaTextStyles.titleLarge, actions: actionButtonLabel == null - ? null + ? [] : [ ZetaButton( label: actionButtonLabel!, diff --git a/lib/src/components/stepper/stepper.dart b/lib/src/components/stepper/stepper.dart index cfa6251b..c6bf2670 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,324 +72,417 @@ 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)), + ), + ), + ], + ], + ), + ), + ) + }, ); } +} - ZetaSemanticColors get _colors => Zeta.of(context).colors; +Color _getElementColor(BuildContext context, bool disabled, bool completed) { + final colors = Zeta.of(context).colors; + Color boxColor = colors.mainPrimary; - bool _isFirst(int index) { - return index == 0; + if (disabled) { + boxColor = colors.mainDisabled; + } 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, + }); - Widget _buildHorizontalIcon(int index) { + /// 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 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.mainInverse, - ), - ZetaStepType.enabled || ZetaStepType.disabled => Text( + color: colors.mainInverse, + ) + : Text( (index + 1).toString(), style: ZetaTextStyles.labelLarge.copyWith( - color: _colors.mainInverse, + color: colors.mainInverse, ), ), - }, - ), ), ); } - 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.mainInverse, - ), - ZetaStepType.enabled || ZetaStepType.disabled => Text( - (index + 1).toString(), - style: ZetaTextStyles.titleLarge.copyWith( - color: _colors.mainInverse, - ), - ), - }, - ), + @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.mainDefault, - ), - ZetaStepType.disabled => ZetaTextStyles.bodySmall.copyWith( - color: _colors.mainDisabled, - ), - }, - 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)); } +} + +/// 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, + }); - Widget _getVerticalHeader(int index) { - final subtitle = widget.steps[index].subtitle; + /// The step that this widget represents. + final ZetaStep step; - return Container( - margin: EdgeInsets.only(top: _isFirst(index) ? 0.0 : Zeta.of(context).spacing.xl_2), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( + /// 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: getLineColor(index), + 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.mainDefault, - ), - ZetaStepType.disabled => ZetaTextStyles.titleLarge.copyWith( - color: _colors.mainDisabled, - ), - }, - maxLines: 1, - duration: kThemeAnimationDuration, - curve: Curves.fastOutSlowIn, - child: widget.steps[index].title, - ), - ], + DefaultTextStyle( + style: ZetaTextStyles.bodySmall.copyWith( + color: step.disabled ? colors.mainDisabled : colors.mainDefault, + ), + 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.mainDisabled, - ZetaStepType.enabled => _colors.mainPrimary, - }; - } +/// 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: getLineColor(index), - ), - ), + 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.mainDisabled : colors.mainDefault, + ), + maxLines: 1, + child: step.title, ), ], - ); - }, + ), + ], ), - }, + ), + ), ); } - Color getLineColor(int index) { - return switch (widget.steps[index].type) { - ZetaStepType.complete => _colors.borderPositive, - ZetaStepType.disabled => _colors.borderDefault, - ZetaStepType.enabled => _colors.borderPrimary, - }; + @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)); } } @@ -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/lib/src/components/top_app_bar/extended_top_app_bar.dart b/lib/src/components/top_app_bar/extended_top_app_bar.dart index c24673c1..54fa3f7a 100644 --- a/lib/src/components/top_app_bar/extended_top_app_bar.dart +++ b/lib/src/components/top_app_bar/extended_top_app_bar.dart @@ -15,7 +15,6 @@ class ZetaExtendedAppBarDelegate extends SliverPersistentHeaderDelegate { required this.shrinks, this.actions, this.leading, - this.searchController, }); /// Title of the app bar. @@ -27,9 +26,6 @@ class ZetaExtendedAppBarDelegate extends SliverPersistentHeaderDelegate { /// Widget displayed first in the app bar row. final Widget? leading; - /// Used to control the search textfield and states. - final ZetaSearchController? searchController; - /// If `ZetaTopAppBarType.extend` shrinks. Does not affect other types of app bar. final bool shrinks; @@ -39,9 +35,7 @@ class ZetaExtendedAppBarDelegate extends SliverPersistentHeaderDelegate { @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { final spacing = Zeta.of(context).spacing; - final colors = Zeta.of(context).colors; - final searchBarOffsetTop = spacing.minimum * 1.5; - final searchBarOffsetRight = spacing.minimum * 22; + final maxExtent = spacing.minimum * 26; final leftMin = spacing.large; final topMin = spacing.xl; @@ -51,37 +45,33 @@ class ZetaExtendedAppBarDelegate extends SliverPersistentHeaderDelegate { /// If there is a leading widget, the left margin should be the same as the leading widget's width plus padding final leftMax = leading == null ? leftMin : _minExtent + spacing.small; + final top = shrinks + ? (topMax + (-1 * shrinkOffset)).clamp( + topMin - (spacing.minimum), + topMax - (spacing.minimum), + ) + : topMax; + return ConstrainedBox( constraints: BoxConstraints(minHeight: spacing.xl_9, maxHeight: maxExtent), child: ColoredBox( - color: colors.surfaceDefault, - child: IconTheme( - data: IconThemeData(color: colors.mainDefault), - child: Stack( - children: [ + color: Zeta.of(context).colors.surfaceDefault, + child: Stack( + children: [ + Positioned( + top: top, + left: shrinks ? ((shrinkOffset / maxExtent) * _maxExtent).clamp(leftMin, leftMax) : leftMin, + right: spacing.none, + child: title, + ), + if (leading != null) Positioned(top: spacing.small, left: spacing.small, child: leading!), + if (actions != null) Positioned( - top: shrinks - ? (topMax + (-1 * shrinkOffset)).clamp( - topMin - - (searchController != null && searchController!.isEnabled - ? searchBarOffsetTop - : Zeta.of(context).spacing.minimum), - topMax, - ) - : topMax, - left: shrinks ? ((shrinkOffset / maxExtent) * _maxExtent).clamp(leftMin, leftMax) : leftMin, - right: searchController != null && searchController!.isEnabled ? searchBarOffsetRight : spacing.none, - child: title, + top: spacing.small, + right: spacing.small, + child: Row(children: actions!), ), - if (leading != null) Positioned(top: spacing.small, left: spacing.small, child: leading!), - if (actions != null) - Positioned( - top: spacing.small, - right: spacing.small, - child: Row(children: actions!), - ), - ], - ), + ], ), ), ); diff --git a/lib/src/components/top_app_bar/search_top_app_bar.dart b/lib/src/components/top_app_bar/search_top_app_bar.dart index 25d093c2..43e1a617 100644 --- a/lib/src/components/top_app_bar/search_top_app_bar.dart +++ b/lib/src/components/top_app_bar/search_top_app_bar.dart @@ -66,20 +66,12 @@ class _ZetaTopAppBarSearchFieldState extends State wit @override void initState() { - _textFocusNode.addListener(_onFocusChanged); widget.searchController?.addListener(_onSearchControllerChanged); widget.searchController?.textEditingController ??= TextEditingController(); super.initState(); } - void _onFocusChanged() { - final text = widget.searchController?.text ?? ''; - final shouldCloseSearch = _isSearching && text.isEmpty && !_textFocusNode.hasFocus; - - if (shouldCloseSearch) _closeSearch(); - } - void _onSearchControllerChanged() { final controller = widget.searchController; if (controller == null) return; @@ -151,7 +143,7 @@ class _ZetaTopAppBarSearchFieldState extends State wit children: [ Row( mainAxisAlignment: - widget.type == ZetaTopAppBarType.centeredTitle ? MainAxisAlignment.center : MainAxisAlignment.start, + widget.type == ZetaTopAppBarType.centered ? MainAxisAlignment.center : MainAxisAlignment.start, children: [ widget.child ?? const Nothing(), ], diff --git a/lib/src/components/top_app_bar/top_app_bar.dart b/lib/src/components/top_app_bar/top_app_bar.dart index 3d51b9a4..2d7f39c3 100644 --- a/lib/src/components/top_app_bar/top_app_bar.dart +++ b/lib/src/components/top_app_bar/top_app_bar.dart @@ -8,6 +8,8 @@ import 'search_top_app_bar.dart'; export 'search_top_app_bar.dart' hide ZetaTopAppBarSearchField; /// Top app bars provide content and actions related to the current screen. +/// +/// To create Extended, Centered, or Search app bars, use the respective constructors. /// {@category Components} /// /// Figma: https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-37&node-type=canvas&m=dev @@ -18,33 +20,76 @@ class ZetaTopAppBar extends ZetaStatefulWidget implements PreferredSizeWidget { const ZetaTopAppBar({ super.key, super.rounded, - this.actions, + this.actions = const [], this.automaticallyImplyLeading = true, - this.searchController, this.leading, this.title, this.titleTextStyle, this.type = ZetaTopAppBarType.defaultAppBar, - this.onSearch, - this.searchHintText = 'Search', - this.onSearchMicrophoneIconPressed, - }) : shrinks = false; + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') ValueChanged? onSearch, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') String? searchHintText, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') ZetaSearchController? searchController, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') + VoidCallback? onSearchMicrophoneIconPressed, + }) : shrinks = false, + onSearch = null, + searchHintText = null, + searchController = null, + clearSemanticLabel = null, + microphoneSemanticLabel = null, + searchBackSemanticLabel = null, + searchSemanticLabel = null, + onSearchMicrophoneIconPressed = null; /// Creates a ZetaTopAppBar with centered title. const ZetaTopAppBar.centered({ super.key, super.rounded, - this.actions, + this.actions = const [], + this.automaticallyImplyLeading = true, + this.leading, + this.title, + this.titleTextStyle, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') ValueChanged? onSearch, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') String? searchHintText, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') ZetaSearchController? searchController, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') + VoidCallback? onSearchMicrophoneIconPressed, + }) : type = ZetaTopAppBarType.centered, + onSearch = null, + searchHintText = null, + searchController = null, + onSearchMicrophoneIconPressed = null, + clearSemanticLabel = null, + searchBackSemanticLabel = null, + microphoneSemanticLabel = null, + searchSemanticLabel = null, + shrinks = false; + + /// Creates a ZetaTopAppBar with an expanding search field. + /// This will append a search icon to the right of the app bar. + /// When the search icon is pressed, the search field will expand and replace the title widget. + /// It will replace the leading widget with a back button which closes the search field. + /// The search field can be controlled externally by the [searchController]. + const ZetaTopAppBar.search({ + super.key, + super.rounded, + this.type = ZetaTopAppBarType.defaultAppBar, this.automaticallyImplyLeading = true, this.searchController, this.leading, this.title, this.titleTextStyle, this.onSearch, - this.searchHintText = 'Search', + this.searchHintText, this.onSearchMicrophoneIconPressed, - }) : type = ZetaTopAppBarType.centeredTitle, - shrinks = false; + this.actions = const [], + this.clearSemanticLabel, + this.microphoneSemanticLabel, + this.searchSemanticLabel, + this.searchBackSemanticLabel, + }) : shrinks = false, + assert(type != ZetaTopAppBarType.extended, 'Search app bars cannot be extended'); /// Creates a ZetaTopAppBar with an extended title over 2 lines. /// @@ -52,23 +97,32 @@ class ZetaTopAppBar extends ZetaStatefulWidget implements PreferredSizeWidget { const ZetaTopAppBar.extended({ super.key, super.rounded, - this.actions, + this.actions = const [], this.automaticallyImplyLeading = true, - this.searchController, this.leading, this.title, this.titleTextStyle, - this.onSearch, - this.searchHintText = 'Search', - this.onSearchMicrophoneIconPressed, this.shrinks = true, - }) : type = ZetaTopAppBarType.extendedTitle; + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') ValueChanged? onSearch, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') String? searchHintText, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') ZetaSearchController? searchController, + @Deprecated('Use ZetaTopAppBar.search instead. ' 'Deprecated as of 0.16.0') + VoidCallback? onSearchMicrophoneIconPressed, + }) : type = ZetaTopAppBarType.extended, + onSearch = null, + searchHintText = null, + onSearchMicrophoneIconPressed = null, + clearSemanticLabel = null, + microphoneSemanticLabel = null, + searchSemanticLabel = null, + searchBackSemanticLabel = null, + searchController = null; /// Called when text in the search field is submitted. - final void Function(String)? onSearch; + final ValueChanged? onSearch; /// A list of Widgets to display in a row after the [title] widget. - final List? actions; + final List actions; /// Configures whether the back button to be displayed. final bool automaticallyImplyLeading; @@ -97,6 +151,18 @@ class ZetaTopAppBar extends ZetaStatefulWidget implements PreferredSizeWidget { /// If `ZetaTopAppBarType.extend` shrinks. Does not affect other types of app bar. final bool shrinks; + /// The semantic label for the clear icon. + final String? clearSemanticLabel; + + /// The semantic label for the microphone icon. + final String? microphoneSemanticLabel; + + /// The semantic label for the search icon. + final String? searchSemanticLabel; + + /// The semantic label for the back icon when search is open. + final String? searchBackSemanticLabel; + @override State createState() => _ZetaTopAppBarState(); @@ -113,34 +179,28 @@ class ZetaTopAppBar extends ZetaStatefulWidget implements PreferredSizeWidget { ..add(DiagnosticsProperty('searchController', searchController)) ..add(StringProperty('searchHintText', searchHintText)) ..add(EnumProperty('type', type)) - ..add(DiagnosticsProperty('shrinks', shrinks)); + ..add(DiagnosticsProperty('shrinks', shrinks)) + ..add(StringProperty('clearSemanticLabel', clearSemanticLabel)) + ..add(StringProperty('microphoneSemanticLabel', microphoneSemanticLabel)) + ..add(StringProperty('searchSemanticLabel', searchSemanticLabel)) + ..add(StringProperty('searchBackSemanticLabel', searchBackSemanticLabel)); } } class _ZetaTopAppBarState extends State { - bool _isSearchEnabled = false; + late ZetaSearchController _searchController; + bool get _searchEnabled => widget.searchController != null || widget.onSearch != null; + bool get _searchActive => _searchController.isEnabled; @override void initState() { - widget.searchController?.addListener(_onSearchControllerChanged); + _searchController = widget.searchController ?? ZetaSearchController(); + _searchController.addListener(() => setState(() {})); super.initState(); } - void _onSearchControllerChanged() { - final controller = widget.searchController; - if (controller == null) return; - - setState(() => _isSearchEnabled = controller.isEnabled); - } - - @override - void dispose() { - widget.searchController?.removeListener(_onSearchControllerChanged); - super.dispose(); - } - - Widget _getTitleText(ZetaSemanticColors colors) { - var title = widget.title; + Widget _getTitle(ZetaSemanticColors colors) { + Widget? title = widget.title; if (widget.title is Row) { final oldRow = widget.title! as Row; title = Row( @@ -169,43 +229,67 @@ class _ZetaTopAppBarState extends State { } List? _getActions(ZetaSemanticColors colors) { - return _isSearchEnabled - ? [ - IconButtonTheme( - data: IconButtonThemeData( - style: IconButton.styleFrom(iconSize: Zeta.of(context).spacing.xl), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - color: colors.mainDefault, - onPressed: () => widget.searchController?.clearText(), - icon: const ZetaIcon(ZetaIcons.cancel), - ), - if (widget.onSearchMicrophoneIconPressed != null) ...[ - SizedBox( - height: Zeta.of(context).spacing.xl_2, - child: VerticalDivider(width: ZetaBorders.medium, color: colors.mainSubtle), - ), - IconButton( - onPressed: widget.onSearchMicrophoneIconPressed, - icon: const ZetaIcon(ZetaIcons.microphone), - ), - ], - ], + if (_searchActive) { + return [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Semantics( + label: widget.clearSemanticLabel, + button: true, + child: IconButton( + color: colors.primitives.cool.shade50, + onPressed: () => _searchController.clearText(), + icon: ZetaIcon( + ZetaIcons.cancel, + size: Zeta.of(context).spacing.xl, + ), ), ), - ] - : widget.actions; + if (widget.onSearchMicrophoneIconPressed != null) ...[ + SizedBox( + height: Zeta.of(context).spacing.xl_2, + child: VerticalDivider(width: ZetaBorders.medium, color: colors.primitives.cool.shade70), + ), + Semantics( + label: widget.microphoneSemanticLabel, + button: true, + child: IconButton( + onPressed: widget.onSearchMicrophoneIconPressed, + icon: const ZetaIcon(ZetaIcons.microphone), + ), + ), + ], + ], + ), + ]; + } + + if (_searchEnabled) { + return [ + ...widget.actions, + Semantics( + label: widget.searchSemanticLabel, + button: true, + child: IconButton( + onPressed: () => setState(() { + _searchController.startSearch(); + }), + icon: const ZetaIcon(ZetaIcons.search), + ), + ), + ]; + } + return widget.actions; } @override Widget build(BuildContext context) { final colors = Zeta.of(context).colors; + final spacing = Zeta.of(context).spacing; final actions = _getActions(colors); - final titleText = _getTitleText(colors); + final titleText = _getTitle(colors); final title = widget.searchController != null ? ZetaTopAppBarSearchField( @@ -213,48 +297,55 @@ class _ZetaTopAppBarState extends State { hintText: widget.searchHintText ?? 'Search', onSearch: widget.onSearch, type: widget.type, - isExtended: widget.type == ZetaTopAppBarType.extendedTitle, + isExtended: widget.type == ZetaTopAppBarType.extended, child: titleText, ) : titleText; - if (widget.type == ZetaTopAppBarType.extendedTitle) { + if (widget.type == ZetaTopAppBarType.extended) { return SliverPersistentHeader( pinned: true, delegate: ZetaExtendedAppBarDelegate( actions: actions, leading: widget.leading, - searchController: widget.searchController, title: title, shrinks: widget.shrinks, ), ); } + Widget? leading = widget.leading; + + if (_searchActive) { + leading = Semantics( + label: widget.searchBackSemanticLabel, + button: true, + child: IconButton( + onPressed: _searchController.closeSearch, + icon: const ZetaIcon(ZetaIcons.arrow_back), + ), + ); + } + return ZetaRoundedScope( rounded: context.rounded, - child: ColoredBox( - color: colors.surfaceDefault, - child: IconButtonTheme( - data: IconButtonThemeData(style: IconButton.styleFrom(tapTargetSize: MaterialTapTargetSize.shrinkWrap)), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: Zeta.of(context).spacing.minimum), - child: AppBar( - elevation: 0, - scrolledUnderElevation: 0, - iconTheme: IconThemeData(color: colors.mainDefault), - leadingWidth: Zeta.of(context).spacing.xl_6, - leading: widget.leading, - automaticallyImplyLeading: widget.automaticallyImplyLeading, - surfaceTintColor: Colors.transparent, - centerTitle: widget.type == ZetaTopAppBarType.centeredTitle, - titleTextStyle: widget.titleTextStyle == null - ? ZetaTextStyles.bodyLarge.copyWith(color: colors.mainDefault) - : widget.titleTextStyle!.copyWith(color: colors.mainDefault), - title: title, - actions: actions, - ), - ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: spacing.minimum), + child: AppBar( + elevation: 0, + scrolledUnderElevation: 0, + backgroundColor: colors.surfaceDefault, + iconTheme: IconThemeData(color: colors.mainDefault), + leading: leading, + toolbarHeight: spacing.xl_9, + automaticallyImplyLeading: widget.automaticallyImplyLeading, + surfaceTintColor: Colors.transparent, + centerTitle: widget.type == ZetaTopAppBarType.centered, + titleTextStyle: widget.titleTextStyle == null + ? ZetaTextStyles.bodyLarge.copyWith(color: colors.mainDefault) + : widget.titleTextStyle!.copyWith(color: colors.mainDefault), + title: title, + actions: actions, ), ), ); @@ -267,8 +358,16 @@ enum ZetaTopAppBarType { defaultAppBar, /// Title in the center. + @Deprecated('Use ZetaTopAppBar.centered instead. ' 'Deprecated as of 0.16.0') centeredTitle, + /// Aligns the title to the center of the app bar. + centered, + /// Title below the app bar. + @Deprecated('Use ZetaTopAppBar.extended instead. ' 'Deprecated as of 0.16.0') extendedTitle, + + /// Title extends over 2 lines and collapses when scrolled. + extended, } diff --git a/pubspec.yaml b/pubspec.yaml index 8b35b798..54d14176 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: zeta_flutter -version: 0.15.2 +version: 0.16.0 description: Zeta is the new, formal, standardized Zebra Design System based off the successes of ZDS (Zebra Design System). This package is in pre-release, and so many aspects are incomplete. diff --git a/test/TESTING_README.mdx b/test/TESTING_README.md similarity index 70% rename from test/TESTING_README.mdx rename to test/TESTING_README.md index 0789e872..6d31f3b5 100644 --- a/test/TESTING_README.mdx +++ b/test/TESTING_README.md @@ -11,24 +11,34 @@ As you are writing tests think about helper function you could write and add the ### Groups -- Accessibility Tests +- **Accessibility Tests** Semantic labels, touch areas, contrast ratios, etc. -- Content Tests - Finds the widget, parameter statuses, etc. -- Dimensions Tests - Size, padding, margin, alignment, etc. -- Styling Tests + +- **Content Tests** + Finds the widget, parameter statuses, etc. + Checking for the value of props and attributes of the widget. Checking for the presence of widgets. + +- **Dimensions Tests** + Size, padding, margin, alignment, etc. + getSize(). + +- **Styling Tests** Rendered colors, fonts, borders, radii etc. -- Interaction Tests + Checking the style of widgets and child widgets. + +- **Interaction Tests** Gesture recognizers, taps, drags, etc. -- Golden Tests + For example, using a boolean to check if the widgets interaction function runs. + +- **Golden Tests** Compares the rendered widget with the golden file. Use the `goldenTest()` function from test_utils/utils.dart. -- Performance Tests + +- **Performance Tests** Animation performance, rendering performance, data manupulation performance, etc. ### Testing File Template -``` +```dart import 'dart:ui'; import 'package:flutter/foundation.dart'; @@ -41,7 +51,6 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const String componentName = 'ENTER_COMPONENT_NAME (e.g. ZetaButton)'; const String parentFolder = 'ENTER_PARENT_FOLDER (e.g. button)'; const goldenFile = GoldenFiles(component: parentFolder); @@ -49,8 +58,9 @@ void main() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('$componentName Accessibility Tests', () {}); - group('$componentName Content Tests', () { + group('Accessibility Tests', () {}); + + group('Content Tests', () { final debugFillProperties = { '': '', }; @@ -59,13 +69,18 @@ void main() { debugFillProperties, ); }); - group('$componentName Dimensions Tests', () {}); - group('$componentName Styling Tests', () {}); - group('$componentName Interaction Tests', () {}); - group('$componentName Golden Tests', () { - goldenTest(goldenFile, widget, widgetType, 'PNG_FILE_NAME'); + + group('Dimensions Tests', () {}); + + group('Styling Tests', () {}); + + group('Interaction Tests', () {}); + + group('Golden Tests', () { + goldenTest(goldenFile, widget, 'PNG_FILE_NAME'); }); - group('$componentName Performance Tests', () {}); + + group('Performance Tests', () {}); } ``` diff --git a/test/scripts/output/test_table.md b/test/scripts/output/test_table.md new file mode 100644 index 00000000..307b28b7 --- /dev/null +++ b/test/scripts/output/test_table.md @@ -0,0 +1,27 @@ +| Component | Accessibility | Content | Dimensions | Styling | Interaction | Golden | Performance | Unorganised | Total Tests | +| -------------- | ------------- | ------- | ---------- | ------- | ----------- | ------ | ----------- | ----------- | ----------- | +| Accordion | 0 | 2 | 0 | 1 | 2 | 0 | 0 | 0 | 5 | +| Avatar | 1 | 3 | 6 | 5 | 0 | 6 | 0 | 0 | 21 | +| Indicator | 0 | 7 | 0 | 0 | 0 | 5 | 0 | 0 | 12 | +| Label | 0 | 8 | 0 | 0 | 0 | 7 | 0 | 0 | 15 | +| Priority Pill | 0 | 5 | 0 | 0 | 0 | 4 | 0 | 0 | 9 | +| Status Label | 0 | 3 | 0 | 0 | 0 | 2 | 0 | 0 | 5 | +| Tag | 0 | 3 | 0 | 0 | 0 | 2 | 0 | 0 | 5 | +| Banner | 3 | 4 | 3 | 3 | 0 | 1 | 0 | 0 | 14 | +| Button | 0 | 10 | 0 | 2 | 1 | 8 | 0 | 0 | 21 | +| Chat Item | 0 | 10 | 0 | 0 | 0 | 8 | 0 | 0 | 18 | +| Checkbox | 0 | 3 | 0 | 0 | 3 | 3 | 0 | 0 | 9 | +| Chip | 0 | 1 | 0 | 0 | 5 | 0 | 0 | 0 | 6 | +| Comms Button | 2 | 7 | 0 | 0 | 0 | 1 | 0 | 0 | 10 | +| Dialpad | 0 | 2 | 0 | 1 | 2 | 3 | 0 | 0 | 8 | +| Fab | 0 | 6 | 0 | 1 | 1 | 6 | 0 | 0 | 14 | +| Icon | 1 | 4 | 1 | 6 | 0 | 0 | 0 | 0 | 12 | +| In Page Banner | 0 | 4 | 0 | 4 | 2 | 4 | 0 | 0 | 14 | +| Password Input | 0 | 4 | 0 | 0 | 0 | 2 | 0 | 0 | 6 | +| Search Bar | 0 | 5 | 0 | 0 | 5 | 5 | 0 | 0 | 15 | +| Slider | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | +| Stepper | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 30 | 30 | +| Stepper Input | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 2 | +| Tooltip | 0 | 3 | 1 | 3 | 0 | 4 | 0 | 0 | 11 | +| Top App Bar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 25 | 25 | +| Total Tests | 7 | 95 | 11 | 26 | 23 | 71 | 0 | 55 | 288 | diff --git a/test/scripts/test_counter.dart b/test/scripts/test_counter.dart index 72dafe74..a74a69d3 100644 --- a/test/scripts/test_counter.dart +++ b/test/scripts/test_counter.dart @@ -124,7 +124,7 @@ class TestVisitor extends RecursiveAstVisitor { } } -/// Generates an MDX (Markdown Extended) table representation of the test counts. +/// Generates an MD (Markdown) table representation of the test counts. /// /// The function takes a nested map where the outer map's keys are test group names, /// and the inner map's keys are test names with their corresponding integer counts. @@ -139,7 +139,7 @@ class TestVisitor extends RecursiveAstVisitor { /// ``` /// /// Example output: -/// ```mdx +/// ```md /// | Component | Accessibility | Content | Dimensions | Styling | Interaction | Golden | Performance | Unorganised | Total Tests | /// | ----------- | ------------- | ------- | ---------- | ------- | ----------- | ------ | ----------- | ----------- | ----------- | /// | Banner | 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | @@ -151,8 +151,8 @@ class TestVisitor extends RecursiveAstVisitor { /// of test names with their corresponding counts. /// /// Returns: -/// - A string in MDX format representing the test counts in a table with totals. -String generateMDX(Map> testCount) { +/// - A string in MD format representing the test counts in a table with totals. +String generateMD(Map> testCount) { final Map groupTotals = { 'Accessibility': 0, 'Content': 0, @@ -228,14 +228,17 @@ void main() async { final TestGroups testGroups = await parseTestFiles(testFiles); // write test groups to file - await writeJSONToFile('${outputDirectory.path}/test_groups.json', testGroups); + // await writeJSONToFile('${outputDirectory.path}/test_groups.json', testGroups); // count the number of tests in each group final TestCount testCount = countTests(testGroups); // write test counts to file - await writeJSONToFile('${outputDirectory.path}/test_counts.json', testCount); + // await writeJSONToFile('${outputDirectory.path}/test_counts.json', testCount); - // generate MDX table - await writeMDXToFile('${outputDirectory.path}/test_table.mdx', generateMDX(testCount)); + // generate MD table + await writeMDToFile('${outputDirectory.path}/test_table.md', generateMD(testCount)); + + // ignore: avoid_print + print('Test table generated successfully!'); } diff --git a/test/scripts/utils/utils.dart b/test/scripts/utils/utils.dart index bcfc30c3..d5796eb3 100644 --- a/test/scripts/utils/utils.dart +++ b/test/scripts/utils/utils.dart @@ -58,11 +58,7 @@ extension NodeExtension on MethodInvocation { /// Returns: /// A [String] containing the sanitized name of the group. String getGroupName() { - return argumentList.arguments.first - .toString() - .replaceAll("'", '') - .replaceAll(r'$componentName ', '') - .replaceAll(' Tests', ''); + return argumentList.arguments.first.toString().replaceAll("'", '').replaceAll(' Tests', ''); } /// Retrieves and sanitizes the name of the test. @@ -185,16 +181,16 @@ Future writeJSONToFile(String path, dynamic content) async { await outputFileGroups.writeAsString(jsonOutputGroups); } -/// Writes the given MDX data to a file at the specified path. +/// Writes the given MD data to a file at the specified path. /// -/// This function asynchronously writes the provided MDX data to a file +/// This function asynchronously writes the provided MD data to a file /// located at the given path. If the file does not exist, it will be created. /// -/// [path] The file path where the MDX data should be written. -/// [mdxData] The MDX data to write to the file. -Future writeMDXToFile(String path, String mdxData) async { - final mdxFile = File(path); - await mdxFile.writeAsString(mdxData); +/// [path] The file path where the MD data should be written. +/// [mdData] The MD data to write to the file. +Future writeMDToFile(String path, String mdData) async { + final mdFile = File(path); + await mdFile.writeAsString(mdData); } extension ListExtension on List { diff --git a/test/src/components/accordion/accordion_test.dart b/test/src/components/accordion/accordion_test.dart index 7bea7792..c170b53a 100644 --- a/test/src/components/accordion/accordion_test.dart +++ b/test/src/components/accordion/accordion_test.dart @@ -1,6 +1,5 @@ import 'dart:ui'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -9,145 +8,154 @@ import '../../../test_utils/test_app.dart'; import '../../../test_utils/utils.dart'; void main() { - testWidgets('ZetaAccordion expands and collapses correctly', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaAccordion( - title: 'Accordion Title', - child: Text('Accordion Content'), - ), + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'title': '"Title"', + 'rounded': 'null', + 'contained': 'false', + 'isOpen': 'false', + }; + debugFillPropertiesTest( + const ZetaAccordion( + title: 'Title', ), + debugFillProperties, ); - // Verify that the accordion is initially collapsed - final Finder accordionContent = find.byType(SizeTransition); - expect(accordionContent, findsOneWidget); - - final SizeTransition sizeTransition = tester.widget(accordionContent); - expect(sizeTransition.sizeFactor.value, 0); - - // Tap on the accordion to expand it - await tester.tap(find.text('Accordion Title')); - await tester.pumpAndSettle(); - - // Verify that the accordion is now expanded - expect(sizeTransition.sizeFactor.value, 1); - - // Tap on the accordion again to collapse it - await tester.tap(find.text('Accordion Title')); - await tester.pumpAndSettle(); + testWidgets('Programatically change child', (WidgetTester tester) async { + Widget? child = const Text('Text 1'); + StateSetter? setState; - expect(sizeTransition.sizeFactor.value, 0); - }); - - testWidgets('ZetaAccordion changes isOpen property correctly', (WidgetTester tester) async { - bool isOpen = false; - StateSetter? setState; - - await tester.pumpWidget( - TestApp( - home: StatefulBuilder( + await tester.pumpWidget( + StatefulBuilder( builder: (context, setState2) { setState = setState2; - return ZetaAccordion( - title: 'Accordion Title', - isOpen: isOpen, - child: const Text('Accordion Content'), + return TestApp( + home: ZetaAccordion( + title: 'Accordion Title', + child: child, + ), ); }, ), - ), - ); - - // Verify that the accordion is initially closed - final Finder accordionContent = find.byType(SizeTransition); - expect(accordionContent, findsOneWidget); - - final SizeTransition sizeTransition = tester.widget(accordionContent); - expect(sizeTransition.sizeFactor.value, 0); - - // Change isOpen property to true - setState?.call(() => isOpen = true); - - await tester.pumpAndSettle(); - - // Verify that the accordion is now open - expect(sizeTransition.sizeFactor.value, 1); - - // Change isOpen property to false - setState?.call(() => isOpen = false); - - await tester.pumpAndSettle(); - - // Verify that the accordion is now closed - expect(sizeTransition.sizeFactor.value, 0); + ); + + final Finder accordionContent = find.text('Text 1'); + expect(accordionContent, findsOneWidget); + setState?.call(() => child = null); + await tester.pumpAndSettle(); + expect(accordionContent, findsNothing); + }); }); + group('Dimensions Tests', () {}); + group('Styling Tests', () { + testWidgets('ZetaAccordion changes color on hover', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaAccordion( + title: 'Accordion Title', + child: Text('Accordion Content'), + ), + ), + ); + + final textButtonFinder = find.byType(TextButton); + final textButton = tester.widget(textButtonFinder); + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byType(ZetaAccordion))); + await tester.pumpAndSettle(); + // Verify that the textButton color matches the hover color + expect( + textButton.style!.overlayColor?.resolve({WidgetState.hovered}), + const ZetaPrimitivesLight().cool.shade20, + ); + expect( + textButton.style!.overlayColor?.resolve({WidgetState.focused}), + Colors.transparent, + ); + expect(textButton.style!.side?.resolve({WidgetState.focused})?.color, const ZetaPrimitivesLight().blue.shade50); + }); + group('Interaction Tests', () { + testWidgets('ZetaAccordion expands and collapses correctly', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaAccordion( + title: 'Accordion Title', + child: Text('Accordion Content'), + ), + ), + ); + + // Verify that the accordion is initially collapsed + final Finder accordionContent = find.byType(SizeTransition); + expect(accordionContent, findsOneWidget); + + final SizeTransition sizeTransition = tester.widget(accordionContent); + expect(sizeTransition.sizeFactor.value, 0); + + // Tap on the accordion to expand it + await tester.tap(find.text('Accordion Title')); + await tester.pumpAndSettle(); + + // Verify that the accordion is now expanded + expect(sizeTransition.sizeFactor.value, 1); + + // Tap on the accordion again to collapse it + await tester.tap(find.text('Accordion Title')); + await tester.pumpAndSettle(); + + expect(sizeTransition.sizeFactor.value, 0); + }); + + testWidgets('ZetaAccordion changes isOpen property correctly', (WidgetTester tester) async { + bool isOpen = false; + StateSetter? setState; + + await tester.pumpWidget( + TestApp( + home: StatefulBuilder( + builder: (context, setState2) { + setState = setState2; + return ZetaAccordion( + title: 'Accordion Title', + isOpen: isOpen, + child: const Text('Accordion Content'), + ); + }, + ), + ), + ); - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaAccordion( - title: 'Title', - ).debugFillProperties(diagnostics); + // Verify that the accordion is initially closed + final Finder accordionContent = find.byType(SizeTransition); + expect(accordionContent, findsOneWidget); - expect(diagnostics.finder('title'), '"Title"'); - expect(diagnostics.finder('rounded'), 'null'); - expect(diagnostics.finder('contained'), 'false'); - expect(diagnostics.finder('isOpen'), 'false'); - }); + final SizeTransition sizeTransition = tester.widget(accordionContent); + expect(sizeTransition.sizeFactor.value, 0); - testWidgets('Programatically change child', (WidgetTester tester) async { - Widget? child = const Text('Text 1'); - StateSetter? setState; + // Change isOpen property to true + setState?.call(() => isOpen = true); - await tester.pumpWidget( - StatefulBuilder( - builder: (context, setState2) { - setState = setState2; - return TestApp( - home: ZetaAccordion( - title: 'Accordion Title', - child: child, - ), - ); - }, - ), - ); + await tester.pumpAndSettle(); - final Finder accordionContent = find.text('Text 1'); - expect(accordionContent, findsOneWidget); - setState?.call(() => child = null); - await tester.pumpAndSettle(); - expect(accordionContent, findsNothing); - }); + // Verify that the accordion is now open + expect(sizeTransition.sizeFactor.value, 1); - testWidgets('ZetaAccordion changes color on hover', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaAccordion( - title: 'Accordion Title', - child: Text('Accordion Content'), - ), - ), - ); + // Change isOpen property to false + setState?.call(() => isOpen = false); - final textButtonFinder = find.byType(TextButton); - final textButton = tester.widget(textButtonFinder); - final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - await gesture.addPointer(location: Offset.zero); - addTearDown(gesture.removePointer); - - await tester.pump(); - await gesture.moveTo(tester.getCenter(find.byType(ZetaAccordion))); - await tester.pumpAndSettle(); - // Verify that the textButton color matches the hover color - expect( - textButton.style!.overlayColor?.resolve({WidgetState.hovered}), - const ZetaPrimitivesLight().cool.shade20, - ); - expect( - textButton.style!.overlayColor?.resolve({WidgetState.focused}), - Colors.transparent, - ); - expect(textButton.style!.side?.resolve({WidgetState.focused})?.color, const ZetaPrimitivesLight().blue.shade50); + await tester.pumpAndSettle(); + + // Verify that the accordion is now closed + expect(sizeTransition.sizeFactor.value, 0); + }); + }); + group('Golden Tests', () {}); + group('Performance Tests', () {}); }); } diff --git a/test/src/components/avatar/avatar_rail_test.dart b/test/src/components/avatar/avatar_rail_test.dart new file mode 100644 index 00000000..3906ba51 --- /dev/null +++ b/test/src/components/avatar/avatar_rail_test.dart @@ -0,0 +1,297 @@ +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'; + +void main() { + final avatarList = [ + const ZetaAvatar.initials( + initials: 'AZ', + label: 'Archie', + ), + const ZetaAvatar.initials( + initials: 'BY', + label: 'Beth', + ), + const ZetaAvatar.initials( + initials: 'CX', + label: 'Clara', + ), + const ZetaAvatar.initials( + initials: 'DW', + label: 'Dan', + ), + const ZetaAvatar.initials( + initials: 'EV', + label: 'Emily', + ), + const ZetaAvatar.initials( + initials: 'FU', + label: 'Frank', + ), + const ZetaAvatar.initials( + initials: 'GT', + label: 'George', + ), + const ZetaAvatar.initials( + initials: 'HS', + label: 'Harith', + ), + const ZetaAvatar.initials( + initials: 'IR', + label: 'Irene', + ), + const ZetaAvatar.initials( + initials: 'KQ', + label: 'Katie', + ), + ]; + + const String parentFolder = 'avatar'; + + const goldenFile = GoldenFiles(component: parentFolder); + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('Accessibility Tests', () { + testWidgets('meets labeled tap target guideline', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaAvatarRail( + avatars: avatarList, + size: ZetaAvatarSize.m, + onTap: (key) {}, + ), + ), + ); + + await expectLater( + tester, + meetsGuideline(labeledTapTargetGuideline), + ); + }); + }); + + group('Content Tests', () { + final debugFillProperties = { + 'labelTextStyle': 'null', + 'size': 'm', + 'labelMaxLines': '2', + 'onTap': 'has onTap', + 'gap': '10.0', + }; + debugFillPropertiesTest( + ZetaAvatarRail( + avatars: avatarList, + size: ZetaAvatarSize.m, + gap: 10, + labelMaxLines: 2, + onTap: (key) {}, + ), + debugFillProperties, + ); + + testWidgets('renders the correct number of avatars', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaAvatarRail( + avatars: avatarList, + size: ZetaAvatarSize.m, + onTap: (key) {}, + ), + ), + ); + + final railFinder = find.byType(ZetaAvatarRail); + final avatarFinder = find.byType(ZetaAvatar); + + expect(railFinder, findsOneWidget); + expect(avatarFinder, findsNWidgets(avatarList.length)); + }); + }); + + group('Dimensions Tests', () { + for (final size in ZetaAvatarSize.values) { + testWidgets('width is correct - avatar size: $size', (WidgetTester tester) async { + const screenWidth = 800.0; + const gap = 10.0; + await tester.pumpWidget( + TestApp( + screenSize: const Size(screenWidth, 600), + home: ZetaAvatarRail( + avatars: avatarList, + size: size, + gap: gap, + onTap: (key) {}, + ), + ), + ); + + final railFinder = find.byType(ZetaAvatarRail); + final avatarFinder = find.byType(ZetaAvatar); + + expect(railFinder, findsOneWidget); + expect(avatarFinder, findsNWidgets(avatarList.length)); + + final railSize = tester.getSize(railFinder); + final avatarSize = tester.getSize(avatarFinder.first); + + if (railSize.width < screenWidth) { + expect(railSize.width, equals(avatarSize.width * avatarList.length + gap * (avatarList.length - 1))); + } else { + expect(railSize.width, equals(screenWidth)); + } + }); + + testWidgets('height is correct - avatar size: $size', (WidgetTester tester) async { + const gap = 10.0; + await tester.pumpWidget( + TestApp( + home: ZetaAvatarRail( + avatars: avatarList, + size: size, + gap: gap, + onTap: (key) {}, + ), + ), + ); + + final railFinder = find.byType(ZetaAvatarRail); + final avatarFinder = find.byType(ZetaAvatar); + + final railSize = tester.getSize(railFinder); + final avatarSize = tester.getSize(avatarFinder.first); + + expect(railSize.height, equals(avatarSize.height)); + }); + } + }); + + group('Styling Tests', () { + testWidgets('applies the correct text style to labels', (WidgetTester tester) async { + const textStyle = TextStyle(color: Colors.red, fontSize: 16); + await tester.pumpWidget( + TestApp( + home: ZetaAvatarRail( + avatars: avatarList, + size: ZetaAvatarSize.m, + labelTextStyle: textStyle, + onTap: (key) {}, + ), + ), + ); + + final labelFinder = find.text('Archie'); + final labelWidget = tester.widget(labelFinder); + + expect(labelWidget.style, equals(textStyle)); + }); + }); + + group('Interaction Tests', () { + final shortList = [ + const ZetaAvatar.initials( + key: Key('avatar1'), + initials: 'AZ', + label: 'Archie', + ), + const ZetaAvatar.initials( + key: Key('avatar2'), + initials: 'BY', + label: 'Beth', + ), + const ZetaAvatar.initials( + key: Key('avatar3'), + initials: 'CX', + label: 'Clara', + ), + ]; + testWidgets('onTap is called when an avatar is tapped', (WidgetTester tester) async { + var tapped = false; + + await tester.pumpWidget( + TestApp( + home: ZetaAvatarRail( + gap: 10, + avatars: shortList, + size: ZetaAvatarSize.m, + onTap: (key) { + tapped = true; + }, + ), + ), + ); + + final avatarFinder = find.byType(ZetaAvatar); + + // Use hitTestable to ensure the widget can receive pointer events + final hitTestableAvatarFinder = avatarFinder.hitTestable(); + + expect(tapped, equals(false)); + + await tester.tap(hitTestableAvatarFinder.first); + await tester.pumpAndSettle(); // Ensure the tap event is processed + + expect(tapped, equals(true)); + }); + + testWidgets('swipe functionality works', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: SizedBox( + height: 70, + width: 300, + child: ZetaAvatarRail( + avatars: avatarList, + size: ZetaAvatarSize.m, + onTap: (key) {}, + ), + ), + ), + ); + + final railFinder = find.byType(ZetaAvatarRail); + expect(railFinder, findsOneWidget); + + await tester.drag(railFinder, const Offset(-300, 0)); + await tester.pumpAndSettle(); + + // Check if the rail has been scrolled + final firstAvatarFinder = find.text('Archie'); + expect(tester.getTopRight(firstAvatarFinder).dx, lessThan(0.0)); + }); + }); + + group('Golden Tests', () { + goldenTest(goldenFile, ZetaAvatarRail(avatars: avatarList), 'zeta_avatar_rail_default'); + }); + + group('Performance Tests', () { + testWidgets( + 'renders within 5 frames', + (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaAvatarRail( + avatars: avatarList, + size: ZetaAvatarSize.m, + onTap: (key) {}, + ), + ), + ); + + final railFinder = find.byType(ZetaAvatarRail); + expect(railFinder, findsOneWidget); + + await tester.pumpAndSettle(); + final frameCount = tester.platformDispatcher.frameData.frameNumber; + expect(frameCount, lessThan(5)); + }, + skip: true, + ); // Skip this test as it is not stable + }); +} diff --git a/test/src/components/avatar/avatar_test.dart b/test/src/components/avatar/avatar_test.dart index 9376f890..10ab2880 100644 --- a/test/src/components/avatar/avatar_test.dart +++ b/test/src/components/avatar/avatar_test.dart @@ -9,13 +9,14 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'avatar'); + const String parentFolder = 'avatar'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaAvatar Accessibility Tests', () { + group('Accessibility Tests', () { testWidgets('ZetaAvatar meets accessibility requirements', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( @@ -43,7 +44,24 @@ void main() { }); }); - group('ZetaAvatar Content Tests', () { + group('Content Tests', () { + final debugFillProperties = { + 'size': 'ZetaAvatarSize.xl', + 'name': 'null', + 'specialStatus': 'null', + 'badge': 'null', + 'backgroundColor': 'null', + 'statusColor': 'null', + 'semanticUpperBadgeValue': '"upperBadge"', + 'semanticValue': '"avatar"', + 'semanticLowerBadgeValue': '"lowerBadge"', + 'initialTextStyle': 'null', + }; + debugFillPropertiesTest( + const ZetaAvatar(), + debugFillProperties, + ); + const names = [ 'John Doe', 'Jane Doe', @@ -96,7 +114,7 @@ void main() { } }); - group('ZetaAvatar Dimensions Tests', () { + group('Dimensions Tests', () { for (final size in ZetaAvatarSize.values) { testWidgets( 'ZetaAvatar size $size with upper badge', @@ -211,7 +229,7 @@ void main() { } }); - group('ZetaAvatar Styling Tests', () { + group('Styling Tests', () { for (final size in ZetaAvatarSize.values) { testWidgets('ZetaAvatar with initials $size text size is correct', (WidgetTester tester) async { await tester.pumpWidget( @@ -295,106 +313,59 @@ void main() { } }); - group('ZetaAvatar Interaction Tests', () {}); + group('Interaction Tests', () {}); - group('ZetaAvatar Golden Tests', () { + group('Golden Tests', () { for (final size in ZetaAvatarSize.values) { - testWidgets('ZetaAvatar default ${size.toString().split('.').last}', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaAvatar( - size: size, - ), - ), - ); - - await expectLater( - find.byType(ZetaAvatar), - matchesGoldenFile(goldenFile.getFileUri('avatar_default_${size.toString().split('.').last}')), - ); - }); - - testWidgets('ZetaAvatar with initials', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaAvatar.initials( - initials: 'AB', - size: size, - ), - ), - ); - - await expectLater( - find.byType(ZetaAvatar), - matchesGoldenFile(goldenFile.getFileUri('avatar_initials_${size.toString().split('.').last}')), - ); - }); - - testWidgets('ZetaAvatar with image', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaAvatar.image( - image: Image.file(File('/assets/maxresdefault.jpg')), - size: size, - ), - ), - ); - - await expectLater( - find.byType(ZetaAvatar), - matchesGoldenFile(goldenFile.getFileUri('avatar_image_${size.toString().split('.').last}')), - ); - }); - - testWidgets('ZetaAvatar with fromName', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaAvatar.fromName( - name: 'John Doe', - size: size, - ), - ), - ); - - await expectLater( - find.byType(ZetaAvatar), - matchesGoldenFile(goldenFile.getFileUri('avatar_from_name_${size.toString().split('.').last}')), - ); - }); - - testWidgets('ZetaAvatar with upper badge', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaAvatar( - upperBadge: const ZetaAvatarBadge.notification(value: 3), - size: size, - ), - ), - ); - - await expectLater( - find.byType(ZetaAvatar), - matchesGoldenFile(goldenFile.getFileUri('avatar_upper_badge_${size.toString().split('.').last}')), - ); - }); - - testWidgets('ZetaAvatar with lower badge', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaAvatar( - lowerBadge: const ZetaAvatarBadge.icon(icon: Icons.star), - size: size, - ), - ), - ); - - await expectLater( - find.byType(ZetaAvatar), - matchesGoldenFile(goldenFile.getFileUri('avatar_lower_badge_${size.toString().split('.').last}')), - ); - }); + goldenTest( + goldenFile, + ZetaAvatar( + size: size, + ), + 'avatar_default_${size.toString().split('.').last}', + ); + goldenTest( + goldenFile, + ZetaAvatar.initials( + initials: 'AB', + size: size, + ), + 'avatar_initials_${size.toString().split('.').last}', + ); + goldenTest( + goldenFile, + ZetaAvatar.image( + image: Image.file(File('/assets/maxresdefault.jpg')), + size: size, + ), + 'avatar_image_${size.toString().split('.').last}', + ); + goldenTest( + goldenFile, + ZetaAvatar.fromName( + name: 'John Doe', + size: size, + ), + 'avatar_from_name_${size.toString().split('.').last}', + ); + goldenTest( + goldenFile, + ZetaAvatar( + upperBadge: const ZetaAvatarBadge.notification(value: 3), + size: size, + ), + 'avatar_upper_badge_${size.toString().split('.').last}', + ); + goldenTest( + goldenFile, + ZetaAvatar( + lowerBadge: const ZetaAvatarBadge.icon(icon: Icons.star), + size: size, + ), + 'avatar_lower_badge_${size.toString().split('.').last}', + ); } }); - group('ZetaAvatar Performance Tests', () {}); + group('Performance Tests', () {}); } diff --git a/test/src/components/avatar/golden/avatar_from_name_l.png b/test/src/components/avatar/golden/avatar_from_name_l.png index f5d2e367..95d82801 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_l.png and b/test/src/components/avatar/golden/avatar_from_name_l.png differ diff --git a/test/src/components/avatar/golden/avatar_from_name_m.png b/test/src/components/avatar/golden/avatar_from_name_m.png index 98772b43..66352451 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_m.png and b/test/src/components/avatar/golden/avatar_from_name_m.png differ diff --git a/test/src/components/avatar/golden/avatar_from_name_s.png b/test/src/components/avatar/golden/avatar_from_name_s.png index d8cdee66..9f6cd1c3 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_s.png and b/test/src/components/avatar/golden/avatar_from_name_s.png differ diff --git a/test/src/components/avatar/golden/avatar_from_name_xl.png b/test/src/components/avatar/golden/avatar_from_name_xl.png index 3c48969d..64b051a8 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_xl.png and b/test/src/components/avatar/golden/avatar_from_name_xl.png differ diff --git a/test/src/components/avatar/golden/avatar_from_name_xs.png b/test/src/components/avatar/golden/avatar_from_name_xs.png index 05dccaa8..df4ae808 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_xs.png and b/test/src/components/avatar/golden/avatar_from_name_xs.png differ diff --git a/test/src/components/avatar/golden/avatar_from_name_xxl.png b/test/src/components/avatar/golden/avatar_from_name_xxl.png index 6dbbed11..6b08259a 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_xxl.png and b/test/src/components/avatar/golden/avatar_from_name_xxl.png differ diff --git a/test/src/components/avatar/golden/avatar_from_name_xxs.png b/test/src/components/avatar/golden/avatar_from_name_xxs.png index 6ec29ad9..37abb6c1 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_xxs.png and b/test/src/components/avatar/golden/avatar_from_name_xxs.png differ diff --git a/test/src/components/avatar/golden/avatar_from_name_xxxl.png b/test/src/components/avatar/golden/avatar_from_name_xxxl.png index 28706197..669eaec1 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_xxxl.png and b/test/src/components/avatar/golden/avatar_from_name_xxxl.png differ diff --git a/test/src/components/avatar/golden/avatar_from_name_xxxs.png b/test/src/components/avatar/golden/avatar_from_name_xxxs.png index d24be80b..3faf7b30 100644 Binary files a/test/src/components/avatar/golden/avatar_from_name_xxxs.png and b/test/src/components/avatar/golden/avatar_from_name_xxxs.png differ diff --git a/test/src/components/avatar/golden/avatar_image_l.png b/test/src/components/avatar/golden/avatar_image_l.png index a7e3c8db..d0294f54 100644 Binary files a/test/src/components/avatar/golden/avatar_image_l.png and b/test/src/components/avatar/golden/avatar_image_l.png differ diff --git a/test/src/components/avatar/golden/avatar_image_m.png b/test/src/components/avatar/golden/avatar_image_m.png index 261256d1..f2884108 100644 Binary files a/test/src/components/avatar/golden/avatar_image_m.png and b/test/src/components/avatar/golden/avatar_image_m.png differ diff --git a/test/src/components/avatar/golden/avatar_image_s.png b/test/src/components/avatar/golden/avatar_image_s.png index e2b0cfe2..2ddd4ca0 100644 Binary files a/test/src/components/avatar/golden/avatar_image_s.png and b/test/src/components/avatar/golden/avatar_image_s.png differ diff --git a/test/src/components/avatar/golden/avatar_image_xl.png b/test/src/components/avatar/golden/avatar_image_xl.png index 57ab8c64..5301fc83 100644 Binary files a/test/src/components/avatar/golden/avatar_image_xl.png and b/test/src/components/avatar/golden/avatar_image_xl.png differ diff --git a/test/src/components/avatar/golden/avatar_image_xs.png b/test/src/components/avatar/golden/avatar_image_xs.png index 909d6b58..e415df34 100644 Binary files a/test/src/components/avatar/golden/avatar_image_xs.png and b/test/src/components/avatar/golden/avatar_image_xs.png differ diff --git a/test/src/components/avatar/golden/avatar_image_xxl.png b/test/src/components/avatar/golden/avatar_image_xxl.png index ff3ec71f..329391f8 100644 Binary files a/test/src/components/avatar/golden/avatar_image_xxl.png and b/test/src/components/avatar/golden/avatar_image_xxl.png differ diff --git a/test/src/components/avatar/golden/avatar_image_xxs.png b/test/src/components/avatar/golden/avatar_image_xxs.png index 81f8f17a..d04d5648 100644 Binary files a/test/src/components/avatar/golden/avatar_image_xxs.png and b/test/src/components/avatar/golden/avatar_image_xxs.png differ diff --git a/test/src/components/avatar/golden/avatar_image_xxxl.png b/test/src/components/avatar/golden/avatar_image_xxxl.png index 71de121a..41529c0d 100644 Binary files a/test/src/components/avatar/golden/avatar_image_xxxl.png and b/test/src/components/avatar/golden/avatar_image_xxxl.png differ diff --git a/test/src/components/avatar/golden/avatar_image_xxxs.png b/test/src/components/avatar/golden/avatar_image_xxxs.png index eda39b62..78814582 100644 Binary files a/test/src/components/avatar/golden/avatar_image_xxxs.png and b/test/src/components/avatar/golden/avatar_image_xxxs.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_l.png b/test/src/components/avatar/golden/avatar_initials_l.png index f5d2e367..95d82801 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_l.png and b/test/src/components/avatar/golden/avatar_initials_l.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_m.png b/test/src/components/avatar/golden/avatar_initials_m.png index 98772b43..66352451 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_m.png and b/test/src/components/avatar/golden/avatar_initials_m.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_s.png b/test/src/components/avatar/golden/avatar_initials_s.png index d8cdee66..9f6cd1c3 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_s.png and b/test/src/components/avatar/golden/avatar_initials_s.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_xl.png b/test/src/components/avatar/golden/avatar_initials_xl.png index 3c48969d..64b051a8 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_xl.png and b/test/src/components/avatar/golden/avatar_initials_xl.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_xs.png b/test/src/components/avatar/golden/avatar_initials_xs.png index 05dccaa8..df4ae808 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_xs.png and b/test/src/components/avatar/golden/avatar_initials_xs.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_xxl.png b/test/src/components/avatar/golden/avatar_initials_xxl.png index 6dbbed11..6b08259a 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_xxl.png and b/test/src/components/avatar/golden/avatar_initials_xxl.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_xxs.png b/test/src/components/avatar/golden/avatar_initials_xxs.png index 6ec29ad9..37abb6c1 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_xxs.png and b/test/src/components/avatar/golden/avatar_initials_xxs.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_xxxl.png b/test/src/components/avatar/golden/avatar_initials_xxxl.png index 28706197..669eaec1 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_xxxl.png and b/test/src/components/avatar/golden/avatar_initials_xxxl.png differ diff --git a/test/src/components/avatar/golden/avatar_initials_xxxs.png b/test/src/components/avatar/golden/avatar_initials_xxxs.png index d24be80b..3faf7b30 100644 Binary files a/test/src/components/avatar/golden/avatar_initials_xxxs.png and b/test/src/components/avatar/golden/avatar_initials_xxxs.png differ diff --git a/test/src/components/avatar/golden/zeta_avatar_rail_default.png b/test/src/components/avatar/golden/zeta_avatar_rail_default.png new file mode 100644 index 00000000..3d3d22f5 Binary files /dev/null and b/test/src/components/avatar/golden/zeta_avatar_rail_default.png differ diff --git a/test/src/components/badge/golden/indicator_icon_default.png b/test/src/components/badge/golden/indicator_icon_default.png index 467559a2..c0175466 100644 Binary files a/test/src/components/badge/golden/indicator_icon_default.png and b/test/src/components/badge/golden/indicator_icon_default.png differ diff --git a/test/src/components/badge/golden/indicator_icon_values.png b/test/src/components/badge/golden/indicator_icon_values.png index 258a474e..5c229116 100644 Binary files a/test/src/components/badge/golden/indicator_icon_values.png and b/test/src/components/badge/golden/indicator_icon_values.png differ diff --git a/test/src/components/badge/golden/label_dark.png b/test/src/components/badge/golden/label_dark.png index d914c07e..4b8db22d 100644 Binary files a/test/src/components/badge/golden/label_dark.png and b/test/src/components/badge/golden/label_dark.png differ diff --git a/test/src/components/badge/golden/label_default.png b/test/src/components/badge/golden/label_default.png index ec970e90..1bcf69d1 100644 Binary files a/test/src/components/badge/golden/label_default.png and b/test/src/components/badge/golden/label_default.png differ diff --git a/test/src/components/badge/golden/label_negative.png b/test/src/components/badge/golden/label_negative.png index b1edfc7d..8fa67842 100644 Binary files a/test/src/components/badge/golden/label_negative.png and b/test/src/components/badge/golden/label_negative.png differ diff --git a/test/src/components/badge/golden/label_neutral.png b/test/src/components/badge/golden/label_neutral.png index e4634281..cabc07d5 100644 Binary files a/test/src/components/badge/golden/label_neutral.png and b/test/src/components/badge/golden/label_neutral.png differ diff --git a/test/src/components/badge/golden/label_positive.png b/test/src/components/badge/golden/label_positive.png index f3404b7b..a62b028b 100644 Binary files a/test/src/components/badge/golden/label_positive.png and b/test/src/components/badge/golden/label_positive.png differ diff --git a/test/src/components/badge/golden/label_sharp.png b/test/src/components/badge/golden/label_sharp.png index f722ff96..35fa7742 100644 Binary files a/test/src/components/badge/golden/label_sharp.png and b/test/src/components/badge/golden/label_sharp.png differ diff --git a/test/src/components/badge/golden/label_warning.png b/test/src/components/badge/golden/label_warning.png index 1c195bf0..2819fec0 100644 Binary files a/test/src/components/badge/golden/label_warning.png and b/test/src/components/badge/golden/label_warning.png differ diff --git a/test/src/components/badge/golden/priority_pill_default.png b/test/src/components/badge/golden/priority_pill_default.png index 145ec8c4..f124b31b 100644 Binary files a/test/src/components/badge/golden/priority_pill_default.png and b/test/src/components/badge/golden/priority_pill_default.png differ diff --git a/test/src/components/badge/golden/priority_pill_high.png b/test/src/components/badge/golden/priority_pill_high.png index 7c2782eb..14857c1c 100644 Binary files a/test/src/components/badge/golden/priority_pill_high.png and b/test/src/components/badge/golden/priority_pill_high.png differ diff --git a/test/src/components/badge/golden/priority_pill_low.png b/test/src/components/badge/golden/priority_pill_low.png index 4fe3e866..a1dd0707 100644 Binary files a/test/src/components/badge/golden/priority_pill_low.png and b/test/src/components/badge/golden/priority_pill_low.png differ diff --git a/test/src/components/badge/golden/priority_pill_medium.png b/test/src/components/badge/golden/priority_pill_medium.png index 076abdf0..838fc291 100644 Binary files a/test/src/components/badge/golden/priority_pill_medium.png and b/test/src/components/badge/golden/priority_pill_medium.png differ diff --git a/test/src/components/badge/golden/status_label_custom.png b/test/src/components/badge/golden/status_label_custom.png index 660ff170..0a758ba0 100644 Binary files a/test/src/components/badge/golden/status_label_custom.png and b/test/src/components/badge/golden/status_label_custom.png differ diff --git a/test/src/components/badge/golden/status_label_default.png b/test/src/components/badge/golden/status_label_default.png index 46407299..2f83b4da 100644 Binary files a/test/src/components/badge/golden/status_label_default.png and b/test/src/components/badge/golden/status_label_default.png differ diff --git a/test/src/components/badge/golden/tag_left.png b/test/src/components/badge/golden/tag_left.png index 4d5aeb99..884848b2 100644 Binary files a/test/src/components/badge/golden/tag_left.png and b/test/src/components/badge/golden/tag_left.png differ diff --git a/test/src/components/badge/golden/tag_right.png b/test/src/components/badge/golden/tag_right.png index 80c4b1c2..ca19d6f6 100644 Binary files a/test/src/components/badge/golden/tag_right.png and b/test/src/components/badge/golden/tag_right.png differ diff --git a/test/src/components/badge/indicator_test.dart b/test/src/components/badge/indicator_test.dart index cf732149..43c683a8 100644 --- a/test/src/components/badge/indicator_test.dart +++ b/test/src/components/badge/indicator_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -7,169 +6,181 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'badge'); + const String parentFolder = 'badge'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - testWidgets('Default constructor initializes with correct parameters', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaIndicator())); - - final zetaIndicatorFinder = find.byType(ZetaIndicator); - final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); - - expect(indicator.rounded, null); - expect(indicator.type, ZetaIndicatorType.notification); - expect(indicator.size, ZetaWidgetSize.large); - expect(indicator.icon, null); - expect(indicator.value, null); - expect(indicator.inverse, false); - expect(indicator.color, null); - - await expectLater( - find.byType(ZetaIndicator), - matchesGoldenFile(goldenFile.getFileUri('indicator_default')), - ); - }); - - testWidgets('Copy with function works', (WidgetTester tester) async { - const Key key1 = Key('1'); - const Key key2 = Key('2'); - const ZetaIndicator one = ZetaIndicator(key: key1); - final ZetaIndicator two = one.copyWith( - key: key2, - ); - await tester.pumpWidget(TestApp(home: Column(children: [one, two]))); - - final zetaIndicatorFinder1 = find.byKey(key1); - final ZetaIndicator indicator1 = tester.firstWidget(zetaIndicatorFinder1); - final zetaIndicatorFinder2 = find.byKey(key2); - final ZetaIndicator indicator2 = tester.firstWidget(zetaIndicatorFinder2); - - expect(indicator1.rounded, null); - expect(indicator1.type, ZetaIndicatorType.notification); - expect(indicator1.size, ZetaWidgetSize.large); - expect(indicator1.icon, null); - expect(indicator1.value, null); - expect(indicator1.inverse, false); - expect(indicator1.color, null); - - expect(indicator2.rounded, null); - }); - testWidgets('Icon constructor initializes with correct parameters', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaIndicator.icon())); - - final zetaIndicatorFinder = find.byType(ZetaIndicator); - final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); - - expect(indicator.rounded, null); - expect(indicator.type, ZetaIndicatorType.icon); - expect(indicator.size, ZetaWidgetSize.large); - expect(indicator.icon, null); - expect(indicator.value, null); - expect(indicator.inverse, false); - expect(indicator.color, null); - - await expectLater( - find.byType(ZetaIndicator), - matchesGoldenFile(goldenFile.getFileUri('indicator_icon_default')), - ); - }); - testWidgets('Icon constructor with values', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaIndicator.icon( - color: Colors.purple, - icon: Icons.abc, - inverse: true, - size: ZetaWidgetSize.medium, - ), + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'color': 'MaterialColor(primary value: Color(0xffff9800))', + 'icon': 'IconData(U+F04B6)', + 'inverse': 'true', + 'rounded': 'false', + 'size': 'ZetaWidgetSize.small', + 'type': 'ZetaIndicatorType.icon', + 'value': '1', + }; + debugFillPropertiesTest( + const ZetaIndicator( + color: Colors.orange, + icon: Icons.abc, + inverse: true, + rounded: false, + size: ZetaWidgetSize.small, + type: ZetaIndicatorType.icon, + value: 1, ), + debugFillProperties, ); - final zetaIndicatorFinder = find.byType(ZetaIndicator); - final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); - - expect(indicator.rounded, null); - expect(indicator.type, ZetaIndicatorType.icon); - expect(indicator.size, ZetaWidgetSize.medium); - expect(indicator.icon, Icons.abc); - expect(indicator.value, null); - expect(indicator.inverse, true); - expect(indicator.color, Colors.purple); - - await expectLater( - find.byType(ZetaIndicator), - matchesGoldenFile(goldenFile.getFileUri('indicator_icon_values')), - ); - }); - testWidgets('Notification constructor initializes with correct parameters', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaIndicator.notification())); - - final zetaIndicatorFinder = find.byType(ZetaIndicator); - final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); - - expect(indicator.rounded, null); - expect(indicator.type, ZetaIndicatorType.notification); - expect(indicator.size, ZetaWidgetSize.large); - expect(indicator.icon, null); - expect(indicator.value, null); - expect(indicator.inverse, false); - expect(indicator.color, null); - - await expectLater( - find.byType(ZetaIndicator), - matchesGoldenFile(goldenFile.getFileUri('indicator_notification_default')), - ); - }); - testWidgets('Notification constructor with values', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaIndicator.notification( - rounded: false, - color: Colors.green, - inverse: true, - size: ZetaWidgetSize.small, - value: 1, + testWidgets('Default constructor initializes with correct parameters', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaIndicator())); + + final zetaIndicatorFinder = find.byType(ZetaIndicator); + final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); + + expect(indicator.rounded, null); + expect(indicator.type, ZetaIndicatorType.notification); + expect(indicator.size, ZetaWidgetSize.large); + expect(indicator.icon, null); + expect(indicator.value, null); + expect(indicator.inverse, false); + expect(indicator.color, null); + }); + + testWidgets('Copy with function works', (WidgetTester tester) async { + const Key key1 = Key('1'); + const Key key2 = Key('2'); + + const ZetaIndicator one = ZetaIndicator(key: key1); + final ZetaIndicator two = one.copyWith( + key: key2, + ); + await tester.pumpWidget(TestApp(home: Column(children: [one, two]))); + + final zetaIndicatorFinder1 = find.byKey(key1); + final ZetaIndicator indicator1 = tester.firstWidget(zetaIndicatorFinder1); + final zetaIndicatorFinder2 = find.byKey(key2); + final ZetaIndicator indicator2 = tester.firstWidget(zetaIndicatorFinder2); + + expect(indicator1.rounded, null); + expect(indicator1.type, ZetaIndicatorType.notification); + expect(indicator1.size, ZetaWidgetSize.large); + expect(indicator1.icon, null); + expect(indicator1.value, null); + expect(indicator1.inverse, false); + expect(indicator1.color, null); + + expect(indicator2.rounded, null); + }); + testWidgets('Icon constructor initializes with correct parameters', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaIndicator.icon())); + + final zetaIndicatorFinder = find.byType(ZetaIndicator); + final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); + + expect(indicator.rounded, null); + expect(indicator.type, ZetaIndicatorType.icon); + expect(indicator.size, ZetaWidgetSize.large); + expect(indicator.icon, null); + expect(indicator.value, null); + expect(indicator.inverse, false); + expect(indicator.color, null); + }); + testWidgets('Icon constructor with values', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaIndicator.icon( + color: Colors.purple, + icon: Icons.abc, + inverse: true, + size: ZetaWidgetSize.medium, + ), ), + ); + + final zetaIndicatorFinder = find.byType(ZetaIndicator); + final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); + + expect(indicator.rounded, null); + expect(indicator.type, ZetaIndicatorType.icon); + expect(indicator.size, ZetaWidgetSize.medium); + expect(indicator.icon, Icons.abc); + expect(indicator.value, null); + expect(indicator.inverse, true); + expect(indicator.color, Colors.purple); + }); + testWidgets('Notification constructor initializes with correct parameters', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaIndicator.notification())); + + final zetaIndicatorFinder = find.byType(ZetaIndicator); + final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); + + expect(indicator.rounded, null); + expect(indicator.type, ZetaIndicatorType.notification); + expect(indicator.size, ZetaWidgetSize.large); + expect(indicator.icon, null); + expect(indicator.value, null); + expect(indicator.inverse, false); + expect(indicator.color, null); + }); + testWidgets('Notification constructor with values', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaIndicator.notification( + rounded: false, + color: Colors.green, + inverse: true, + size: ZetaWidgetSize.small, + value: 1, + ), + ), + ); + + final zetaIndicatorFinder = find.byType(ZetaIndicator); + final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); + + expect(indicator.rounded, false); + expect(indicator.type, ZetaIndicatorType.notification); + expect(indicator.size, ZetaWidgetSize.small); + expect(indicator.icon, null); + expect(indicator.value, 1); + expect(indicator.inverse, true); + expect(indicator.color, Colors.green); + }); + }); + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { + goldenTest(goldenFile, const ZetaIndicator(), 'indicator_default'); + goldenTest(goldenFile, const ZetaIndicator.icon(), 'indicator_icon_default'); + goldenTest( + goldenFile, + const ZetaIndicator.icon( + color: Colors.purple, + icon: Icons.abc, + inverse: true, + size: ZetaWidgetSize.medium, ), + 'indicator_icon_values', ); - - final zetaIndicatorFinder = find.byType(ZetaIndicator); - final ZetaIndicator indicator = tester.firstWidget(zetaIndicatorFinder); - - expect(indicator.rounded, false); - expect(indicator.type, ZetaIndicatorType.notification); - expect(indicator.size, ZetaWidgetSize.small); - expect(indicator.icon, null); - expect(indicator.value, 1); - expect(indicator.inverse, true); - expect(indicator.color, Colors.green); - - await expectLater( - find.byType(ZetaIndicator), - matchesGoldenFile(goldenFile.getFileUri('indicator_notification_values')), + goldenTest(goldenFile, const ZetaIndicator.notification(), 'indicator_notification_default'); + goldenTest( + goldenFile, + const ZetaIndicator.notification( + rounded: false, + color: Colors.green, + inverse: true, + size: ZetaWidgetSize.small, + value: 1, + ), + 'indicator_notification_values', ); }); - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaIndicator( - color: Colors.orange, - icon: Icons.abc, - inverse: true, - rounded: false, - size: ZetaWidgetSize.small, - type: ZetaIndicatorType.icon, - value: 1, - ).debugFillProperties(diagnostics); - - expect(diagnostics.finder('color'), 'MaterialColor(primary value: Color(0xffff9800))'); - expect(diagnostics.finder('icon'), 'IconData(U+F04B6)'); - expect(diagnostics.finder('inverse'), 'true'); - expect(diagnostics.finder('rounded'), 'false'); - expect(diagnostics.finder('size'), 'ZetaWidgetSize.small'); - expect(diagnostics.finder('type'), 'ZetaIndicatorType.icon'); - expect(diagnostics.finder('value'), '1'); - }); + group('Performance Tests', () {}); } diff --git a/test/src/components/badge/label_test.dart b/test/src/components/badge/label_test.dart index d5bfb5d2..24d1e99b 100644 --- a/test/src/components/badge/label_test.dart +++ b/test/src/components/badge/label_test.dart @@ -1,111 +1,132 @@ -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'; void main() { - const goldenFile = GoldenFiles(component: 'badge'); + const String parentFolder = 'badge'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - testWidgets('Initializes with correct parameters', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label'))); - - final zetaBadgeFinder = find.byType(ZetaLabel); - final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - expect(label.rounded, null); - expect(label.label, 'Test Label'); - expect(label.status, ZetaWidgetStatus.info); + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'label': '"Test label"', + 'status': 'positive', + 'rounded': 'false', + }; + debugFillPropertiesTest( + const ZetaLabel( + label: 'Test label', + rounded: false, + status: ZetaWidgetStatus.positive, + ), + debugFillProperties, + ); - await expectLater(find.byType(ZetaLabel), matchesGoldenFile(goldenFile.getFileUri('label_default'))); - }); + testWidgets('Initializes with correct parameters', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label'))); - testWidgets('Positive status', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.positive))); + final zetaBadgeFinder = find.byType(ZetaLabel); + final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - final zetaBadgeFinder = find.byType(ZetaLabel); - final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); + expect(label.rounded, null); + expect(label.label, 'Test Label'); + expect(label.status, ZetaWidgetStatus.info); + }); - expect(label.status, ZetaWidgetStatus.positive); + testWidgets('Positive status', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.positive))); - await expectLater(find.byType(ZetaLabel), matchesGoldenFile(goldenFile.getFileUri('label_positive'))); - }); + final zetaBadgeFinder = find.byType(ZetaLabel); + final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - testWidgets('Warning status', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.warning))); + expect(label.status, ZetaWidgetStatus.positive); + }); - final zetaBadgeFinder = find.byType(ZetaLabel); - final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); + testWidgets('Warning status', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.warning))); - expect(label.status, ZetaWidgetStatus.warning); + final zetaBadgeFinder = find.byType(ZetaLabel); + final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - await expectLater(find.byType(ZetaLabel), matchesGoldenFile(goldenFile.getFileUri('label_warning'))); - }); - testWidgets('Negative status', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.negative))); + expect(label.status, ZetaWidgetStatus.warning); + }); + testWidgets('Negative status', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.negative))); - final zetaBadgeFinder = find.byType(ZetaLabel); - final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); + final zetaBadgeFinder = find.byType(ZetaLabel); + final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - expect(label.status, ZetaWidgetStatus.negative); + expect(label.status, ZetaWidgetStatus.negative); + }); + testWidgets('Neutral status', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.neutral))); - await expectLater(find.byType(ZetaLabel), matchesGoldenFile(goldenFile.getFileUri('label_negative'))); - }); - testWidgets('Neutral status', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.neutral))); + final zetaBadgeFinder = find.byType(ZetaLabel); + final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - final zetaBadgeFinder = find.byType(ZetaLabel); - final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); + expect(label.status, ZetaWidgetStatus.neutral); + }); - expect(label.status, ZetaWidgetStatus.neutral); + testWidgets('Dark mode', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + themeMode: ThemeMode.dark, + home: ZetaLabel(label: 'Test Label'), + ), + ); - await expectLater(find.byType(ZetaLabel), matchesGoldenFile(goldenFile.getFileUri('label_neutral'))); - }); + final zetaBadgeFinder = find.byType(ZetaLabel); + final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - testWidgets('Dark mode', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - themeMode: ThemeMode.dark, - home: ZetaLabel(label: 'Test Label'), - ), - ); + expect(label.status, ZetaWidgetStatus.info); + }); - final zetaBadgeFinder = find.byType(ZetaLabel); - final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); + testWidgets('Sharp', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp(home: ZetaLabel(label: 'Test Label', rounded: false)), + ); - expect(label.status, ZetaWidgetStatus.info); + final zetaBadgeFinder = find.byType(ZetaLabel); + final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - await expectLater(find.byType(ZetaLabel), matchesGoldenFile(goldenFile.getFileUri('label_dark'))); + expect(label.rounded, false); + }); }); - - testWidgets('Sharp', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp(home: ZetaLabel(label: 'Test Label', rounded: false)), + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { + goldenTest(goldenFile, const ZetaLabel(label: 'Test Label'), 'label_default'); + goldenTest( + goldenFile, + const ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.positive), + 'label_positive', ); - - final zetaBadgeFinder = find.byType(ZetaLabel); - final ZetaLabel label = tester.firstWidget(zetaBadgeFinder); - - expect(label.rounded, false); - - await expectLater(find.byType(ZetaLabel), matchesGoldenFile(goldenFile.getFileUri('label_sharp'))); - }); - - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaLabel( - label: 'Test label', - rounded: false, - status: ZetaWidgetStatus.positive, - ).debugFillProperties(diagnostics); - - expect(diagnostics.finder('label'), '"Test label"'); - expect(diagnostics.finder('status'), 'positive'); - expect(diagnostics.finder('rounded'), 'false'); + goldenTest( + goldenFile, + const ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.warning), + 'label_warning', + ); + goldenTest( + goldenFile, + const ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.negative), + 'label_negative', + ); + goldenTest( + goldenFile, + const ZetaLabel(label: 'Test Label', status: ZetaWidgetStatus.neutral), + 'label_neutral', + ); + goldenTest(goldenFile, const ZetaLabel(label: 'Test Label'), 'label_dark', themeMode: ThemeMode.dark); + goldenTest(goldenFile, const ZetaLabel(label: 'Test Label', rounded: false), 'label_sharp'); }); + group('Performance Tests', () {}); } diff --git a/test/src/components/badge/priority_pill_test.dart b/test/src/components/badge/priority_pill_test.dart index 90b6c056..9725772e 100644 --- a/test/src/components/badge/priority_pill_test.dart +++ b/test/src/components/badge/priority_pill_test.dart @@ -8,104 +8,147 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'badge'); + const String parentFolder = 'badge'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - testWidgets('Initializes with correct label and index', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaPriorityPill(), + + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'label': '"Test label"', + 'rounded': 'false', + 'isBadge': 'false', + 'index': '"1"', + 'customColor': const ZetaPrimitivesLight().blue.toString(), + 'type': 'urgent', + 'size': 'large', + }; + debugFillPropertiesTest( + ZetaPriorityPill( + label: 'Test label', + rounded: false, + customColor: const ZetaPrimitivesLight().blue, + index: '1', ), + debugFillProperties, ); - final zetaPriorityPillFinder = find.byType(ZetaPriorityPill); - final ZetaPriorityPill zetaPriorityPill = tester.firstWidget(zetaPriorityPillFinder); - expect(zetaPriorityPill.rounded, null); - expect(zetaPriorityPill.index, null); - expect(zetaPriorityPill.isBadge, false); - expect(zetaPriorityPill.customColor, null); - expect(zetaPriorityPill.label, null); - expect(zetaPriorityPill.size, ZetaPriorityPillSize.large); - expect(zetaPriorityPill.type, ZetaPriorityPillType.urgent); + testWidgets('Initializes with correct label and index', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaPriorityPill(), + ), + ); - expect(find.text('Urgent'), findsOneWidget); - expect(find.text('U'), findsOneWidget); + final zetaPriorityPillFinder = find.byType(ZetaPriorityPill); + final ZetaPriorityPill zetaPriorityPill = tester.firstWidget(zetaPriorityPillFinder); + expect(zetaPriorityPill.rounded, null); + expect(zetaPriorityPill.index, null); + expect(zetaPriorityPill.isBadge, false); + expect(zetaPriorityPill.customColor, null); + expect(zetaPriorityPill.label, null); + expect(zetaPriorityPill.size, ZetaPriorityPillSize.large); + expect(zetaPriorityPill.type, ZetaPriorityPillType.urgent); - await expectLater( - find.byType(ZetaPriorityPill), - matchesGoldenFile(goldenFile.getFileUri('priority_pill_default')), - ); - }); - testWidgets('High priority pill', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaPriorityPill( - type: ZetaPriorityPillType.high, - index: '10', - label: 'test label', - rounded: false, - size: ZetaPriorityPillSize.small, - ), - ), - ); + expect(find.text('Urgent'), findsOneWidget); + expect(find.text('U'), findsOneWidget); - final zetaPriorityPillFinder = find.byType(ZetaPriorityPill); - final ZetaPriorityPill zetaPriorityPill = tester.firstWidget(zetaPriorityPillFinder); - expect(zetaPriorityPill.rounded, false); - expect(zetaPriorityPill.index, '10'); - expect(zetaPriorityPill.isBadge, false); - expect(zetaPriorityPill.type, ZetaPriorityPillType.high); - expect(zetaPriorityPill.size, ZetaPriorityPillSize.small); + await expectLater( + find.byType(ZetaPriorityPill), + matchesGoldenFile(goldenFile.getFileUri('priority_pill_default')), + ); + }); + testWidgets('High priority pill', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaPriorityPill( + type: ZetaPriorityPillType.high, + index: '10', + label: 'test label', + rounded: false, + size: ZetaPriorityPillSize.small, + ), + ), + ); - expect(find.text('test label'), findsOneWidget); + final zetaPriorityPillFinder = find.byType(ZetaPriorityPill); + final ZetaPriorityPill zetaPriorityPill = tester.firstWidget(zetaPriorityPillFinder); + expect(zetaPriorityPill.rounded, false); + expect(zetaPriorityPill.index, '10'); + expect(zetaPriorityPill.isBadge, false); + expect(zetaPriorityPill.type, ZetaPriorityPillType.high); + expect(zetaPriorityPill.size, ZetaPriorityPillSize.small); - await expectLater( - find.byType(ZetaPriorityPill), - matchesGoldenFile(goldenFile.getFileUri('priority_pill_high')), - ); - }); - testWidgets('Medium priority pill', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaPriorityPill( - type: ZetaPriorityPillType.medium, - isBadge: true, + expect(find.text('test label'), findsOneWidget); + }); + testWidgets('Medium priority pill', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaPriorityPill( + type: ZetaPriorityPillType.medium, + isBadge: true, + ), ), - ), - ); + ); - final zetaPriorityPillFinder = find.byType(ZetaPriorityPill); - final ZetaPriorityPill zetaPriorityPill = tester.firstWidget(zetaPriorityPillFinder); - expect(zetaPriorityPill.type, ZetaPriorityPillType.medium); - expect(zetaPriorityPill.isBadge, true); + final zetaPriorityPillFinder = find.byType(ZetaPriorityPill); + final ZetaPriorityPill zetaPriorityPill = tester.firstWidget(zetaPriorityPillFinder); + expect(zetaPriorityPill.type, ZetaPriorityPillType.medium); + expect(zetaPriorityPill.isBadge, true); + }); + testWidgets('Low priority pill', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaPriorityPill( + type: ZetaPriorityPillType.low, + size: ZetaPriorityPillSize.small, + isBadge: true, + ), + ), + ); - await expectLater( - find.byType(ZetaPriorityPill), - matchesGoldenFile(goldenFile.getFileUri('priority_pill_medium')), - ); + final zetaPriorityPillFinder = find.byType(ZetaPriorityPill); + final ZetaPriorityPill zetaPriorityPill = tester.firstWidget(zetaPriorityPillFinder); + expect(zetaPriorityPill.type, ZetaPriorityPillType.low); + expect(zetaPriorityPill.isBadge, true); + expect(zetaPriorityPill.size, ZetaPriorityPillSize.small); + }); }); - testWidgets('Low priority pill', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaPriorityPill( - type: ZetaPriorityPillType.low, - size: ZetaPriorityPillSize.small, - isBadge: true, - ), + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { + goldenTest(goldenFile, const ZetaPriorityPill(), 'priority_pill_default'); + goldenTest( + goldenFile, + const ZetaPriorityPill( + type: ZetaPriorityPillType.high, + index: '10', + label: 'test label', + rounded: false, + size: ZetaPriorityPillSize.small, ), + 'priority_pill_high', ); - - final zetaPriorityPillFinder = find.byType(ZetaPriorityPill); - final ZetaPriorityPill zetaPriorityPill = tester.firstWidget(zetaPriorityPillFinder); - expect(zetaPriorityPill.type, ZetaPriorityPillType.low); - expect(zetaPriorityPill.isBadge, true); - expect(zetaPriorityPill.size, ZetaPriorityPillSize.small); - - await expectLater( - find.byType(ZetaPriorityPill), - matchesGoldenFile(goldenFile.getFileUri('priority_pill_low')), + goldenTest( + goldenFile, + const ZetaPriorityPill( + type: ZetaPriorityPillType.medium, + isBadge: true, + ), + 'priority_pill_medium', + ); + goldenTest( + goldenFile, + const ZetaPriorityPill( + type: ZetaPriorityPillType.low, + size: ZetaPriorityPillSize.small, + isBadge: true, + ), + 'priority_pill_low', ); }); testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { diff --git a/test/src/components/badge/status_label_test.dart b/test/src/components/badge/status_label_test.dart index 201a8ebc..992f6bad 100644 --- a/test/src/components/badge/status_label_test.dart +++ b/test/src/components/badge/status_label_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -8,12 +7,30 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'badge'); + const String parentFolder = 'badge'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaStatusLabel Tests', () { + + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'label': '"Test label"', + 'rounded': 'false', + 'customIcon': 'IconData(U+F04B6)', + 'status': 'info', + }; + debugFillPropertiesTest( + const ZetaStatusLabel( + label: 'Test label', + rounded: false, + customIcon: Icons.abc, + ), + debugFillProperties, + ); + testWidgets('Initializes with correct properties', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( @@ -21,42 +38,34 @@ void main() { ), ); expect(find.text('Test Label'), findsOneWidget); + }); - await expectLater( - find.byType(ZetaStatusLabel), - matchesGoldenFile(goldenFile.getFileUri('status_label_default')), + testWidgets('Initializes with correct label and custom icon', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaStatusLabel( + label: 'Custom Icon', + customIcon: Icons.person, + ), + ), ); + expect(find.text('Custom Icon'), findsOneWidget); + expect(find.byIcon(Icons.person), findsOneWidget); }); }); - - testWidgets('Initializes with correct label and custom icon', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaStatusLabel( - label: 'Custom Icon', - customIcon: Icons.person, - ), + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { + goldenTest(goldenFile, const ZetaStatusLabel(label: 'Test Label'), 'status_label_default'); + goldenTest( + goldenFile, + const ZetaStatusLabel( + label: 'Custom Icon', + customIcon: Icons.person, ), + 'status_label_custom', ); - expect(find.text('Custom Icon'), findsOneWidget); - expect(find.byIcon(Icons.person), findsOneWidget); - - await expectLater( - find.byType(ZetaStatusLabel), - matchesGoldenFile(goldenFile.getFileUri('status_label_custom')), - ); - }); - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaStatusLabel( - label: 'Test label', - rounded: false, - customIcon: Icons.abc, - ).debugFillProperties(diagnostics); - - expect(diagnostics.finder('label'), '"Test label"'); - expect(diagnostics.finder('rounded'), 'false'); - expect(diagnostics.finder('customIcon'), 'IconData(U+F04B6)'); - expect(diagnostics.finder('status'), 'info'); }); + group('Performance Tests', () {}); } diff --git a/test/src/components/badge/tag_test.dart b/test/src/components/badge/tag_test.dart index 3dd84531..dc1cdba5 100644 --- a/test/src/components/badge/tag_test.dart +++ b/test/src/components/badge/tag_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -7,12 +6,28 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'badge'); + const String parentFolder = 'badge'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaTag', () { + + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'label': '"Test label"', + 'rounded': 'false', + 'direction': 'left', + }; + debugFillPropertiesTest( + const ZetaTag( + label: 'Test label', + rounded: false, + ), + debugFillProperties, + ); + testWidgets('Initializes right with correct parameters', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( @@ -21,13 +36,7 @@ void main() { ); expect(find.text('Tag'), findsOneWidget); - - await expectLater( - find.byType(ZetaTag), - matchesGoldenFile(goldenFile.getFileUri('tag_right')), - ); }); - testWidgets('Initializes left with correct parameters', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( @@ -35,23 +44,14 @@ void main() { ), ); expect(find.byType(ZetaTag), findsOneWidget); - - await expectLater( - find.byType(ZetaTag), - matchesGoldenFile(goldenFile.getFileUri('tag_left')), - ); }); }); - - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaTag( - label: 'Test label', - rounded: false, - ).debugFillProperties(diagnostics); - - expect(diagnostics.finder('label'), '"Test label"'); - expect(diagnostics.finder('rounded'), 'false'); - expect(diagnostics.finder('direction'), 'left'); + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { + goldenTest(goldenFile, const ZetaTag.right(label: 'Tag', rounded: false), 'tag_right'); + goldenTest(goldenFile, const ZetaTag.left(label: 'Tag', rounded: true), 'tag_left'); }); + group('Performance Tests', () {}); } diff --git a/test/src/components/banner/banner_test.dart b/test/src/components/banner/banner_test.dart index 4e1e2765..9d1399ec 100644 --- a/test/src/components/banner/banner_test.dart +++ b/test/src/components/banner/banner_test.dart @@ -23,13 +23,15 @@ ZetaColorSwatch _colorFromType(BuildContext context, ZetaBannerStatus type) { } void main() { - const goldenFile = GoldenFiles(component: 'banner'); + const String parentFolder = 'banner'; + + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaBanner Accessibility Tests', () { + group('Accessibility Tests', () { for (final type in ZetaBannerStatus.values) { testWidgets('meets contrast ratio guideline for $type', (WidgetTester tester) async { await tester.pumpWidget( @@ -118,7 +120,7 @@ void main() { }); }); - group('ZetaBanner Content Tests', () { + group('Content Tests', () { testWidgets('ZetaBanner title is correct', (WidgetTester tester) async { await tester.pumpWidget( TestApp( @@ -192,7 +194,7 @@ void main() { }); }); - group('ZetaBanner Dimension Tests', () { + group('Dimensions Tests', () { testWidgets('icon is the correct size', (WidgetTester tester) async { await tester.pumpWidget( TestApp( @@ -256,7 +258,7 @@ void main() { }); }); - group('ZetaBanner Styling Tests', () { + group('Styling Tests', () { for (final type in ZetaBannerStatus.values) { testWidgets('title styles are correct for $type', (WidgetTester tester) async { await tester.pumpWidget( @@ -328,32 +330,28 @@ void main() { } }); - group('ZetaBanner Interaction Tests', () {}); + group('Interaction Tests', () {}); - group('ZetaBanner Golden Tests', () { + group('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}')), - ); - }); + goldenTest( + goldenFile, + Builder( + builder: (context) { + return ZetaBanner( + context: context, + title: 'Banner Title', + leadingIcon: Icons.info, + trailing: const ZetaIcon(Icons.chevron_right), + type: type, + ); + }, + ), + 'banner_${type.toString().split('.').last}', + widgetType: ZetaBanner, + ); } }); + + group('Performace Tests', () {}); } diff --git a/test/src/components/button/button_test.dart b/test/src/components/button/button_test.dart index d7647981..07b32f51 100644 --- a/test/src/components/button/button_test.dart +++ b/test/src/components/button/button_test.dart @@ -10,13 +10,30 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'button'); + const String parentFolder = 'button'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaButton Tests', () { + group('Accessibility Tests', () {}); + + group('Content Tests', () { + final debugFillProperties = { + 'label': '"Test label"', + 'onPressed': 'null', + 'type': 'primary', + 'borderType': 'null', + 'size': 'medium', + 'leadingIcon': 'null', + 'trailingIcon': 'null', + }; + debugFillPropertiesTest( + const ZetaButton(label: 'Test label'), + debugFillProperties, + ); + testWidgets('Initializes with correct Label', (WidgetTester tester) async { await tester.pumpWidget( TestApp( @@ -26,163 +43,267 @@ void main() { expect(find.text('Test Button'), findsOneWidget); }); - }); - testWidgets('Triggers callback on tap', (WidgetTester tester) async { - bool callbackTriggered = false; - await tester.pumpWidget( - TestApp(home: ZetaButton(onPressed: () => callbackTriggered = true, label: 'Test Button')), - ); - await tester.tap(find.byType(ZetaButton)); - await tester.pump(); + testWidgets('Initializes primary with correct Label', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaButton.primary(onPressed: () {}, label: 'Test Button'), + ), + ); - expect(callbackTriggered, isTrue); - }); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + expect(button.borderType, null); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, null); + expect(button.size, ZetaWidgetSize.medium); + expect(button.type, ZetaButtonType.primary); + }); - testWidgets('Initializes primary with correct Label', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaButton.primary(onPressed: () {}, label: 'Test Button'), - ), - ); + testWidgets('Initializes secondary with correct Label', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaButton.secondary( + onPressed: () {}, + label: 'Test Button', + leadingIcon: Icons.abc, + size: ZetaWidgetSize.small, + ), + ), + ); - final buttonFinder = find.byType(ZetaButton); - final ZetaButton button = tester.firstWidget(buttonFinder); - expect(button.borderType, null); - expect(button.label, 'Test Button'); - expect(button.leadingIcon, null); - expect(button.trailingIcon, null); - expect(button.size, ZetaWidgetSize.medium); - expect(button.type, ZetaButtonType.primary); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + expect(button.borderType, null); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, Icons.abc); + expect(button.trailingIcon, null); + expect(button.size, ZetaWidgetSize.small); + expect(button.type, ZetaButtonType.secondary); + }); + testWidgets('Initializes positive with correct Label', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaButton.positive(onPressed: () {}, label: 'Test Button', trailingIcon: Icons.abc), + ), + ); - await expectLater(find.byType(ZetaButton), matchesGoldenFile(goldenFile.getFileUri('button_primary'))); - }); - testWidgets('Initializes secondary with correct Label', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaButton.secondary( - onPressed: () {}, - label: 'Test Button', - leadingIcon: Icons.abc, - size: ZetaWidgetSize.small, + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + expect(button.borderType, null); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, Icons.abc); + expect(button.size, ZetaWidgetSize.medium); + expect(button.type, ZetaButtonType.positive); + }); + testWidgets('Initializes negative with correct Label', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaButton.negative(onPressed: () {}, label: 'Test Button', size: ZetaWidgetSize.small), ), - ), - ); + ); - final buttonFinder = find.byType(ZetaButton); - final ZetaButton button = tester.firstWidget(buttonFinder); - expect(button.borderType, null); - expect(button.label, 'Test Button'); - expect(button.leadingIcon, Icons.abc); - expect(button.trailingIcon, null); - expect(button.size, ZetaWidgetSize.small); - expect(button.type, ZetaButtonType.secondary); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + expect(button.borderType, null); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, null); + expect(button.size, ZetaWidgetSize.small); + expect(button.type, ZetaButtonType.negative); + }); + testWidgets('Initializes outline with correct Label', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaButton.outline(onPressed: () {}, label: 'Test Button', size: ZetaWidgetSize.large), + ), + ); - await expectLater( - find.byType(ZetaButton), - matchesGoldenFile(goldenFile.getFileUri('button_secondary')), - ); - }); - testWidgets('Initializes positive with correct Label', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaButton.positive(onPressed: () {}, label: 'Test Button', trailingIcon: Icons.abc), - ), - ); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + expect(button.borderType, null); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, null); + expect(button.size, ZetaWidgetSize.large); + expect(button.type, ZetaButtonType.outline); + }); + testWidgets('Initializes outlineSubtle with correct Label', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaButton.outlineSubtle(label: 'Test Button', borderType: ZetaWidgetBorder.sharp), + ), + ); - final buttonFinder = find.byType(ZetaButton); - final ZetaButton button = tester.firstWidget(buttonFinder); - expect(button.borderType, null); - expect(button.label, 'Test Button'); - expect(button.leadingIcon, null); - expect(button.trailingIcon, Icons.abc); - expect(button.size, ZetaWidgetSize.medium); - expect(button.type, ZetaButtonType.positive); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + expect(button.borderType, ZetaWidgetBorder.sharp); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, null); + expect(button.size, ZetaWidgetSize.medium); + expect(button.type, ZetaButtonType.outlineSubtle); + }); + testWidgets('Initializes text with correct Label', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaButton.text(onPressed: () {}, label: 'Test Button', borderType: ZetaWidgetBorder.full), + ), + ); - await expectLater( - find.byType(ZetaButton), - matchesGoldenFile(goldenFile.getFileUri('button_positive')), - ); - }); - testWidgets('Initializes negative with correct Label', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaButton.negative(onPressed: () {}, label: 'Test Button', size: ZetaWidgetSize.small), - ), - ); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + expect(button.borderType, ZetaWidgetBorder.full); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, null); + expect(button.size, ZetaWidgetSize.medium); + expect(button.type, ZetaButtonType.text); + }); - final buttonFinder = find.byType(ZetaButton); - final ZetaButton button = tester.firstWidget(buttonFinder); - expect(button.borderType, null); - expect(button.label, 'Test Button'); - expect(button.leadingIcon, null); - expect(button.trailingIcon, null); - expect(button.size, ZetaWidgetSize.small); - expect(button.type, ZetaButtonType.negative); + testWidgets('Disabled button', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaButton.text(label: 'Test Button', borderType: ZetaWidgetBorder.full), + ), + ); - await expectLater( - find.byType(ZetaButton), - matchesGoldenFile(goldenFile.getFileUri('button_negative')), - ); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + expect(button.borderType, ZetaWidgetBorder.full); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, null); + expect(button.onPressed, null); + expect(button.size, ZetaWidgetSize.medium); + expect(button.type, ZetaButtonType.text); + }); }); - testWidgets('Initializes outline with correct Label', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaButton.outline(onPressed: () {}, label: 'Test Button', size: ZetaWidgetSize.large), - ), - ); - final buttonFinder = find.byType(ZetaButton); - final ZetaButton button = tester.firstWidget(buttonFinder); - expect(button.borderType, null); - expect(button.label, 'Test Button'); - expect(button.leadingIcon, null); - expect(button.trailingIcon, null); - expect(button.size, ZetaWidgetSize.large); - expect(button.type, ZetaButtonType.outline); + group('Dimensions Tests', () {}); - await expectLater(find.byType(ZetaButton), matchesGoldenFile(goldenFile.getFileUri('button_outline'))); - }); - testWidgets('Initializes outlineSubtle with correct Label', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: ZetaButton.outlineSubtle(label: 'Test Button', borderType: ZetaWidgetBorder.sharp), - ), - ); + group('Styling Tests', () { + testWidgets('Hover states are correct', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaButton(label: 'Test Button', onPressed: () {}), + ), + ); - final buttonFinder = find.byType(ZetaButton); - final ZetaButton button = tester.firstWidget(buttonFinder); - expect(button.borderType, ZetaWidgetBorder.sharp); - expect(button.label, 'Test Button'); - expect(button.leadingIcon, null); - expect(button.trailingIcon, null); - expect(button.size, ZetaWidgetSize.medium); - expect(button.type, ZetaButtonType.outlineSubtle); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); - await expectLater( - find.byType(ZetaButton), - matchesGoldenFile(goldenFile.getFileUri('button_outline_subtle')), - ); + final filledButtonFinder = find.byType(FilledButton); + final FilledButton filledButton = tester.firstWidget(filledButtonFinder); + + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byType(ZetaButton))); + await tester.pumpAndSettle(); + + expect( + filledButton.style?.backgroundColor?.resolve({WidgetState.hovered}), + const ZetaPrimitivesLight().blue.shade50, + ); + + await gesture.down(tester.getCenter(find.byType(ZetaButton))); + await tester.pumpAndSettle(); + expect( + filledButton.style?.backgroundColor?.resolve({WidgetState.pressed}), + const ZetaPrimitivesLight().blue.shade70, + ); + + await gesture.up(); + + await tester.pumpAndSettle(); + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, null); + expect(button.size, ZetaWidgetSize.medium); + }); + + testWidgets('Focus state is correct', (WidgetTester tester) async { + final FocusNode focusNode = FocusNode(); + await tester.pumpWidget( + TestApp( + home: ZetaButton(label: 'Test Button', onPressed: () {}, focusNode: focusNode), + ), + ); + final buttonFinder = find.byType(ZetaButton); + final ZetaButton button = tester.firstWidget(buttonFinder); + focusNode.requestFocus(); + await tester.pump(); + final filledButtonFinder = find.byType(FilledButton); + final FilledButton filledButton = tester.firstWidget(filledButtonFinder); + + expect(button.label, 'Test Button'); + expect(button.leadingIcon, null); + expect(button.trailingIcon, null); + expect(button.size, ZetaWidgetSize.medium); + expect( + filledButton.style?.side?.resolve({WidgetState.focused}), + BorderSide(color: const ZetaPrimitivesLight().blue.shade50, width: ZetaBorders.medium), + ); + }); }); - testWidgets('Initializes text with correct Label', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaButton.text(onPressed: () {}, label: 'Test Button', borderType: ZetaWidgetBorder.full), - ), - ); - final buttonFinder = find.byType(ZetaButton); - final ZetaButton button = tester.firstWidget(buttonFinder); - expect(button.borderType, ZetaWidgetBorder.full); - expect(button.label, 'Test Button'); - expect(button.leadingIcon, null); - expect(button.trailingIcon, null); - expect(button.size, ZetaWidgetSize.medium); - expect(button.type, ZetaButtonType.text); + group('Interaction Tests', () { + testWidgets('Triggers callback on tap', (WidgetTester tester) async { + bool callbackTriggered = false; + await tester.pumpWidget( + TestApp(home: ZetaButton(onPressed: () => callbackTriggered = true, label: 'Test Button')), + ); + await tester.tap(find.byType(ZetaButton)); + await tester.pump(); - await expectLater( - find.byType(ZetaButton), - matchesGoldenFile(goldenFile.getFileUri('button_text')), + expect(callbackTriggered, isTrue); + }); + }); + + group('Golden Tests', () { + goldenTest( + goldenFile, + ZetaButton.primary(onPressed: () {}, label: 'Test Button'), + 'button_primary', + ); + goldenTest( + goldenFile, + ZetaButton.secondary(onPressed: () {}, label: 'Test Button', leadingIcon: Icons.abc, size: ZetaWidgetSize.small), + 'button_secondary', + ); + goldenTest( + goldenFile, + ZetaButton.positive(onPressed: () {}, label: 'Test Button', trailingIcon: Icons.abc), + 'button_positive', + ); + goldenTest( + goldenFile, + ZetaButton.negative(onPressed: () {}, label: 'Test Button', size: ZetaWidgetSize.small), + 'button_negative', + ); + goldenTest( + goldenFile, + ZetaButton.outline(onPressed: () {}, label: 'Test Button', size: ZetaWidgetSize.large), + 'button_outline', + ); + goldenTest( + goldenFile, + const ZetaButton.outlineSubtle(label: 'Test Button', borderType: ZetaWidgetBorder.sharp), + 'button_outline_subtle', + ); + goldenTest( + goldenFile, + ZetaButton.text(onPressed: () {}, label: 'Test Button', borderType: ZetaWidgetBorder.full), + 'button_text', + ); + goldenTest( + goldenFile, + const ZetaButton.text(label: 'Test Button', borderType: ZetaWidgetBorder.full), + 'button_disabled', ); }); diff --git a/test/src/components/button/golden/button_disabled.png b/test/src/components/button/golden/button_disabled.png index 3bdef324..6b987856 100644 Binary files a/test/src/components/button/golden/button_disabled.png and b/test/src/components/button/golden/button_disabled.png differ diff --git a/test/src/components/button/golden/button_negative.png b/test/src/components/button/golden/button_negative.png index d11d7166..1f094054 100644 Binary files a/test/src/components/button/golden/button_negative.png and b/test/src/components/button/golden/button_negative.png differ diff --git a/test/src/components/button/golden/button_outline.png b/test/src/components/button/golden/button_outline.png index 8a8c0849..429abc43 100644 Binary files a/test/src/components/button/golden/button_outline.png and b/test/src/components/button/golden/button_outline.png differ diff --git a/test/src/components/button/golden/button_outline_subtle.png b/test/src/components/button/golden/button_outline_subtle.png index 87649e6b..536c285b 100644 Binary files a/test/src/components/button/golden/button_outline_subtle.png and b/test/src/components/button/golden/button_outline_subtle.png differ diff --git a/test/src/components/button/golden/button_positive.png b/test/src/components/button/golden/button_positive.png index 3f11e853..568473b1 100644 Binary files a/test/src/components/button/golden/button_positive.png and b/test/src/components/button/golden/button_positive.png differ diff --git a/test/src/components/button/golden/button_primary.png b/test/src/components/button/golden/button_primary.png index c6d4e28f..63c8cb3a 100644 Binary files a/test/src/components/button/golden/button_primary.png and b/test/src/components/button/golden/button_primary.png differ diff --git a/test/src/components/button/golden/button_secondary.png b/test/src/components/button/golden/button_secondary.png index a606c85f..6178262d 100644 Binary files a/test/src/components/button/golden/button_secondary.png and b/test/src/components/button/golden/button_secondary.png differ diff --git a/test/src/components/button/golden/button_text.png b/test/src/components/button/golden/button_text.png index 0a1785f5..7276f3b3 100644 Binary files a/test/src/components/button/golden/button_text.png and b/test/src/components/button/golden/button_text.png differ diff --git a/test/src/components/chat_item/chat_item_test.dart b/test/src/components/chat_item/chat_item_test.dart index 8efd5c0d..daacf68f 100644 --- a/test/src/components/chat_item/chat_item_test.dart +++ b/test/src/components/chat_item/chat_item_test.dart @@ -25,7 +25,60 @@ void main() { when(mockZetaColors.primitives).thenReturn(const ZetaPrimitivesLight()); }); - group('ZetaChatItem Tests', () { + group('Accessibility Tests', () {}); + group('Content Tests', () { + final time = DateTime.now(); + final debugFillPropertiesChatItem = { + 'rounded': 'null', + 'highlighted': 'true', + 'time': time.toString(), + 'timeFormat': 'null', + 'enabledWarningIcon': 'true', + 'enabledNotificationIcon': 'true', + 'count': '1', + 'onTap': 'null', + 'starred': 'true', + 'onMenuMoreTap': 'null', + 'onCallTap': 'null', + 'onDeleteTap': 'null', + 'onPttTap': 'null', + 'explicitChildNodes': 'true', + 'paleButtonColors': 'null', + }; + debugFillPropertiesTest( + ZetaChatItem( + title: const Text('Title'), + subtitle: const Text('Subtitle'), + time: time, + leading: const ZetaAvatar(initials: 'AZ'), + slidableActions: [ + ZetaSlidableAction.menuMore(onPressed: () {}), + ZetaSlidableAction.call(onPressed: () {}), + ZetaSlidableAction.ptt(onPressed: () {}), + ZetaSlidableAction.delete(onPressed: () {}), + ], + count: 1, + enabledNotificationIcon: true, + highlighted: true, + enabledWarningIcon: true, + starred: true, + ), + debugFillPropertiesChatItem, + ); + final debugFillPropertiesSlidableAction = { + 'onPressed': 'null', + 'icon': 'IconData(U+0E5F9)', + 'foregroundColor': null, + 'backgroundColor': null, + 'color': 'null', + 'semanticLabel': 'null', + 'paleColor': 'false', + }; + debugFillPropertiesTest( + const ZetaSlidableAction(icon: Icons.star), + debugFillPropertiesSlidableAction, + ); + testWidgets('ZetaChatItem displays correctly', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(481, 480); @@ -74,11 +127,6 @@ void main() { await tester.tap(chatItemFinder); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); - - await expectLater( - chatItemFinder, - matchesGoldenFile(goldenFile.getFileUri('chat_item_default')), - ); }); testWidgets('ZetaChatItem highlighted', (WidgetTester tester) async { @@ -129,11 +177,6 @@ void main() { await tester.tap(chatItemFinder); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); - - await expectLater( - chatItemFinder, - matchesGoldenFile(goldenFile.getFileUri('chat_item_highlighted')), - ); }); testWidgets('ZetaChatItem slidable actions', (WidgetTester tester) async { @@ -191,11 +234,6 @@ void main() { await tester.tap(find.byIcon(ZetaIcons.delete_round)); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); - - await expectLater( - chatItemFinder, - matchesGoldenFile(goldenFile.getFileUri('chat_item_slidable_actions')), - ); }); testWidgets('ZetaChatItem with pale slidable button colors', (WidgetTester tester) async { @@ -245,11 +283,6 @@ void main() { expect(find.byIcon(ZetaIcons.phone_round), findsOneWidget); expect(find.byIcon(ZetaIcons.ptt_round), findsOneWidget); expect(find.byIcon(ZetaIcons.delete_round), findsOneWidget); - - await expectLater( - chatItemFinder, - matchesGoldenFile(goldenFile.getFileUri('chat_item_pale_slidable_buttons')), - ); }); testWidgets('ZetaChatItem with 2 pale buttons and 2 regular buttons', (WidgetTester tester) async { @@ -333,11 +366,6 @@ void main() { await tester.tap(find.byIcon(Icons.message)); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); - - await expectLater( - chatItemFinder, - matchesGoldenFile(goldenFile.getFileUri('chat_item_pale_and_regular_buttons')), - ); }); testWidgets('ZetaChatItem with custom leading widget', (WidgetTester tester) async { @@ -372,16 +400,9 @@ void main() { ), ); - final chatItemFinder = find.byType(ZetaChatItem); - // Verify that the custom leading widget is displayed correctly expect(find.byType(Container), findsOneWidget); expect(find.byWidgetPredicate((widget) => widget is Container && widget.color == Colors.blue), findsOneWidget); - - await expectLater( - chatItemFinder, - matchesGoldenFile(goldenFile.getFileUri('chat_item_custom_leading')), - ); }); testWidgets('ZetaChatItem with custom slidable buttons', (WidgetTester tester) async { @@ -551,11 +572,298 @@ void main() { await tester.tap(find.byIcon(ZetaIcons.delete_round)); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); - - await expectLater( - chatItemFinder, - matchesGoldenFile(goldenFile.getFileUri('chat_item_small_screen_slidable_button')), - ); }); }); + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { + const title = Text('John Doe'); + const subtitle = Text('Hello, how are you?'); + final time = DateTime.now(); + + goldenTest( + goldenFile, + Scaffold( + body: Column( + children: [ + ZetaChatItem( + explicitChildNodes: false, + time: time, + enabledWarningIcon: true, + enabledNotificationIcon: true, + leading: const ZetaAvatar(initials: 'AZ'), + count: 100, + onTap: () {}, + paleButtonColors: true, + slidableActions: [ + ZetaSlidableAction.menuMore(onPressed: () {}), + ZetaSlidableAction.call(onPressed: () {}), + ZetaSlidableAction.ptt(onPressed: () {}), + ZetaSlidableAction.delete(onPressed: () {}), + ], + title: title, + subtitle: subtitle, + ), + ], + ), + ), + 'chat_item_default', + widgetType: ZetaChatItem, + setUp: (tester) async { + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(481, 480); + }, + ); + + goldenTest( + goldenFile, + Column( + children: [ + ZetaChatItem( + explicitChildNodes: false, + time: time, + enabledWarningIcon: true, + enabledNotificationIcon: true, + leading: const ZetaAvatar(initials: 'AZ'), + count: 100, + onTap: () {}, + paleButtonColors: false, + starred: true, + slidableActions: [ + ZetaSlidableAction.menuMore(onPressed: () {}), + ZetaSlidableAction.call(onPressed: () {}), + ZetaSlidableAction.ptt(onPressed: () {}), + ZetaSlidableAction.delete(onPressed: () {}), + ], + highlighted: true, + title: title, + subtitle: subtitle, + ), + ], + ), + 'chat_item_highlighted', + widgetType: ZetaChatItem, + setUp: (tester) async { + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(481, 480); + }, + ); + + goldenTest( + goldenFile, + Column( + children: [ + ZetaChatItem( + time: time, + leading: const ZetaAvatar(initials: 'AZ'), + slidableActions: [ + ZetaSlidableAction.menuMore(onPressed: () {}), + ZetaSlidableAction.call(onPressed: () {}), + ZetaSlidableAction.ptt(onPressed: () {}), + ZetaSlidableAction.delete(onPressed: () {}), + ], + title: title, + subtitle: subtitle, + ), + ], + ), + 'chat_item_slidable_actions', + widgetType: ZetaChatItem, + setUp: (tester) async { + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(481, 480); + }, + beforeComparison: (tester) async { + final chatItemFinder = find.byType(ZetaChatItem); + await tester.drag(chatItemFinder, const Offset(-200, 0)); + await tester.pumpAndSettle(); + }, + ); + + goldenTest( + goldenFile, + Column( + children: [ + ZetaChatItem( + time: time, + leading: const ZetaAvatar(initials: 'AZ'), + paleButtonColors: true, + slidableActions: [ + ZetaSlidableAction.menuMore( + onPressed: () {}, + ), + ZetaSlidableAction.call( + onPressed: () {}, + ), + ZetaSlidableAction.ptt( + onPressed: () {}, + ), + ZetaSlidableAction.delete( + onPressed: () {}, + ), + ], + title: title, + subtitle: subtitle, + ), + ], + ), + 'chat_item_pale_slidable_buttons', + widgetType: ZetaChatItem, + setUp: (tester) async { + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(481, 480); + }, + beforeComparison: (tester) async { + final chatItemFinder = find.byType(ZetaChatItem); + await tester.drag(chatItemFinder, const Offset(-200, 0)); + await tester.pumpAndSettle(); + }, + ); + + goldenTest( + goldenFile, + Column( + children: [ + ZetaChatItem( + time: time, + leading: const ZetaAvatar(initials: 'AZ'), + slidableActions: [ + ZetaSlidableAction( + onPressed: () {}, + paleColor: true, + icon: Icons.star, + ), + ZetaSlidableAction( + onPressed: () {}, + paleColor: true, + icon: Icons.delete, + ), + ZetaSlidableAction( + onPressed: () {}, + icon: Icons.call, + ), + ZetaSlidableAction( + onPressed: () {}, + icon: Icons.message, + ), + ], + title: title, + subtitle: subtitle, + ), + ], + ), + 'chat_item_pale_and_regular_buttons', + widgetType: ZetaChatItem, + setUp: (tester) async { + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(481, 480); + }, + beforeComparison: (tester) async { + final chatItemFinder = find.byType(ZetaChatItem); + await tester.drag(chatItemFinder, const Offset(-200, 0)); + await tester.pumpAndSettle(); + }, + ); + + goldenTest( + goldenFile, + Column( + children: [ + ZetaChatItem( + time: time, + leading: Container( + width: 40, + height: 40, + color: Colors.blue, + ), + slidableActions: [ + ZetaSlidableAction.menuMore(onPressed: () {}), + ZetaSlidableAction.call(onPressed: () {}), + ZetaSlidableAction.ptt(onPressed: () {}), + ZetaSlidableAction.delete(onPressed: () {}), + ], + title: title, + subtitle: subtitle, + ), + ], + ), + 'chat_item_custom_leading', + widgetType: ZetaChatItem, + setUp: (tester) async { + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(481, 480); + }, + ); + + goldenTest( + goldenFile, + Column( + children: [ + ZetaChatItem( + time: time, + leading: const ZetaAvatar(initials: 'AZ'), + slidableActions: [ + ZetaSlidableAction( + onPressed: () {}, + color: const ZetaPrimitivesLight().orange, + icon: Icons.star, + ), + ZetaSlidableAction( + onPressed: () {}, + color: const ZetaPrimitivesLight().red, + icon: Icons.delete, + ), + ], + title: title, + subtitle: subtitle, + ), + ], + ), + 'chat_item_custom_slidable_buttons', + widgetType: ZetaChatItem, + setUp: (tester) async { + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(481, 480); + }, + beforeComparison: (tester) async { + final chatItemFinder = find.byType(ZetaChatItem); + await tester.drag(chatItemFinder, const Offset(-200, 0)); + await tester.pumpAndSettle(); + }, + ); + + goldenTest( + goldenFile, + Column( + children: [ + ZetaChatItem( + time: time, + leading: const ZetaAvatar(initials: 'AZ'), + slidableActions: [ + ZetaSlidableAction.menuMore(onPressed: () {}), + ZetaSlidableAction.call(onPressed: () {}), + ZetaSlidableAction.ptt(onPressed: () {}), + ZetaSlidableAction.delete(onPressed: () {}), + ], + title: title, + count: 1, + subtitle: subtitle, + ), + ], + ), + 'chat_item_small_screen_slidable_button', + widgetType: ZetaChatItem, + setUp: (tester) async { + tester.view.devicePixelRatio = 1.0; + tester.view.physicalSize = const Size(315, 480); + }, + beforeComparison: (tester) async { + final chatItemFinder = find.byType(ZetaChatItem); + await tester.drag(chatItemFinder, const Offset(-200, 0)); + await tester.pumpAndSettle(); + }, + ); + }); + group('Performance Tests', () {}); } diff --git a/test/src/components/chat_item/golden/chat_item_custom_leading.png b/test/src/components/chat_item/golden/chat_item_custom_leading.png index a7e88db9..df190ca2 100644 Binary files a/test/src/components/chat_item/golden/chat_item_custom_leading.png and b/test/src/components/chat_item/golden/chat_item_custom_leading.png differ diff --git a/test/src/components/chat_item/golden/chat_item_custom_slidable_buttons.png b/test/src/components/chat_item/golden/chat_item_custom_slidable_buttons.png index 76afaeb3..0b49bfc6 100644 Binary files a/test/src/components/chat_item/golden/chat_item_custom_slidable_buttons.png and b/test/src/components/chat_item/golden/chat_item_custom_slidable_buttons.png differ diff --git a/test/src/components/chat_item/golden/chat_item_default.png b/test/src/components/chat_item/golden/chat_item_default.png index e5144d5c..a0e31005 100644 Binary files a/test/src/components/chat_item/golden/chat_item_default.png and b/test/src/components/chat_item/golden/chat_item_default.png differ diff --git a/test/src/components/chat_item/golden/chat_item_highlighted.png b/test/src/components/chat_item/golden/chat_item_highlighted.png index 36dc6d77..88987b0a 100644 Binary files a/test/src/components/chat_item/golden/chat_item_highlighted.png and b/test/src/components/chat_item/golden/chat_item_highlighted.png differ diff --git a/test/src/components/chat_item/golden/chat_item_pale_and_regular_buttons.png b/test/src/components/chat_item/golden/chat_item_pale_and_regular_buttons.png index ae8b7949..b6ec9565 100644 Binary files a/test/src/components/chat_item/golden/chat_item_pale_and_regular_buttons.png and b/test/src/components/chat_item/golden/chat_item_pale_and_regular_buttons.png differ diff --git a/test/src/components/chat_item/golden/chat_item_pale_slidable_buttons.png b/test/src/components/chat_item/golden/chat_item_pale_slidable_buttons.png index 2872c4e7..21d704e8 100644 Binary files a/test/src/components/chat_item/golden/chat_item_pale_slidable_buttons.png and b/test/src/components/chat_item/golden/chat_item_pale_slidable_buttons.png differ diff --git a/test/src/components/chat_item/golden/chat_item_slidable_actions.png b/test/src/components/chat_item/golden/chat_item_slidable_actions.png index 3a6faae9..4931862e 100644 Binary files a/test/src/components/chat_item/golden/chat_item_slidable_actions.png and b/test/src/components/chat_item/golden/chat_item_slidable_actions.png differ diff --git a/test/src/components/chat_item/golden/chat_item_small_screen_slidable_button.png b/test/src/components/chat_item/golden/chat_item_small_screen_slidable_button.png index e372ca21..fe8a0b88 100644 Binary files a/test/src/components/chat_item/golden/chat_item_small_screen_slidable_button.png and b/test/src/components/chat_item/golden/chat_item_small_screen_slidable_button.png differ diff --git a/test/src/components/checkbox/checkbox_test.dart b/test/src/components/checkbox/checkbox_test.dart index 9488ea20..12c0534b 100644 --- a/test/src/components/checkbox/checkbox_test.dart +++ b/test/src/components/checkbox/checkbox_test.dart @@ -1,6 +1,5 @@ import 'dart:ui'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/src/components/checkbox/checkbox.dart'; @@ -11,13 +10,41 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'checkbox'); + const String parentFolder = 'checkbox'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaCheckbox Tests', () { + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillPropertiesCheckbox = { + 'value': 'false', + 'label': 'null', + 'onChanged': 'null', + 'rounded': 'null', + 'useIndeterminate': 'false', + 'focusNode': 'null', + }; + debugFillPropertiesTest( + ZetaCheckbox(), + debugFillPropertiesCheckbox, + ); + final debugFillPropertiesCheckboxInternal = { + 'value': 'false', + 'label': 'null', + 'rounded': 'null', + 'useIndeterminate': 'false', + 'error': 'false', + 'disabled': 'false', + 'focusNode': 'null', + }; + debugFillPropertiesTest( + ZetaInternalCheckbox(onChanged: (_) {}), + debugFillPropertiesCheckboxInternal, + ); + testWidgets('Initializes with correct parameters', (WidgetTester tester) async { await tester.pumpWidget( TestApp( @@ -36,7 +63,10 @@ void main() { expect(checkbox.rounded, null); expect(checkbox.label, 'Test Checkbox'); }); - + }); + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () { testWidgets('ZetaCheckbox changes state on tap', (WidgetTester tester) async { bool? checkboxValue = true; @@ -51,11 +81,6 @@ void main() { ), ); - final checkboxFinder = find.byType(ZetaCheckbox); - await expectLater( - checkboxFinder, - matchesGoldenFile(goldenFile.getFileUri('checkbox_enabled')), - ); await tester.tap(find.byType(ZetaCheckbox)); await tester.pump(); @@ -71,10 +96,7 @@ void main() { ); final checkboxFinder = find.byType(ZetaCheckbox); - await expectLater( - checkboxFinder, - matchesGoldenFile(goldenFile.getFileUri('checkbox_disabled')), - ); + await tester.tap(find.byType(ZetaCheckbox)); await tester.pump(); final ZetaCheckbox checkbox = tester.firstWidget(checkboxFinder); @@ -118,51 +140,43 @@ void main() { await tester.tap(find.byType(ZetaCheckbox)); await tester.pump(); }); - - testWidgets('ZetaCheckbox UI changes on hover', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaCheckbox( - onChanged: (value) {}, - ), - ), - ); - - final checkboxFinder = find.byType(ZetaCheckbox); - - // Hover state - final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - await gesture.addPointer(location: Offset.zero); - addTearDown(gesture.removePointer); - await tester.pump(); - await gesture.moveTo(tester.getCenter(checkboxFinder)); - await tester.pumpAndSettle(); - await expectLater( - checkboxFinder, - matchesGoldenFile(goldenFile.getFileUri('checkbox_hover')), - ); - }); - - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - ZetaCheckbox().debugFillProperties(diagnostics); - - expect(diagnostics.finder('value'), 'false'); - expect(diagnostics.finder('label'), 'null'); - expect(diagnostics.finder('onChanged'), 'null'); - expect(diagnostics.finder('rounded'), 'null'); - expect(diagnostics.finder('useIndeterminate'), 'false'); - expect(diagnostics.finder('focusNode'), 'null'); - - final internalDiagnostics = DiagnosticPropertiesBuilder(); - ZetaInternalCheckbox(onChanged: (_) {}).debugFillProperties(internalDiagnostics); - expect(internalDiagnostics.finder('value'), 'false'); - expect(internalDiagnostics.finder('label'), 'null'); - expect(internalDiagnostics.finder('rounded'), 'null'); - expect(internalDiagnostics.finder('useIndeterminate'), 'false'); - expect(internalDiagnostics.finder('error'), 'false'); - expect(internalDiagnostics.finder('disabled'), 'false'); - expect(internalDiagnostics.finder('focusNode'), 'null'); - }); }); + group('Golden Tests', () { + goldenTest( + goldenFile, + ZetaCheckbox( + onChanged: (value) {}, + ), + 'checkbox_hover', + beforeComparison: (tester) async { + final checkboxFinder = find.byType(ZetaCheckbox); + + // Hover state + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(tester.getCenter(checkboxFinder)); + await tester.pumpAndSettle(); + }, + ); + + goldenTest( + goldenFile, + ZetaCheckbox( + value: true, + onChanged: print, + ), + 'checkbox_enabled', + ); + + goldenTest( + goldenFile, + ZetaCheckbox( + value: true, + ), + 'checkbox_disabled', + ); + }); + group('Performance Tests', () {}); } diff --git a/test/src/components/checkbox/golden/checkbox_disabled.png b/test/src/components/checkbox/golden/checkbox_disabled.png index 765624c1..6f974562 100644 Binary files a/test/src/components/checkbox/golden/checkbox_disabled.png and b/test/src/components/checkbox/golden/checkbox_disabled.png differ diff --git a/test/src/components/checkbox/golden/checkbox_enabled.png b/test/src/components/checkbox/golden/checkbox_enabled.png index 3d23b46f..05d65049 100644 Binary files a/test/src/components/checkbox/golden/checkbox_enabled.png and b/test/src/components/checkbox/golden/checkbox_enabled.png differ diff --git a/test/src/components/chips/chip_test.dart b/test/src/components/chips/chip_test.dart index 0d1eb2d1..9a9781e2 100644 --- a/test/src/components/chips/chip_test.dart +++ b/test/src/components/chips/chip_test.dart @@ -3,9 +3,27 @@ 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'; void main() { - group('ZetaChip', () { + const String parentFolder = 'chips'; + + const goldenFile = GoldenFiles(component: parentFolder); + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('Accessibility Tests', () {}); + group('Content Tests', () { + // final debugFillProperties = { + // '': '', + // }; + // debugFillPropertiesTest( + // widget, + // debugFillProperties, + // ); + testWidgets('renders label correctly', (WidgetTester tester) async { await tester.pumpWidget( const TestApp(home: ZetaChip(label: 'Test Chip')), @@ -13,7 +31,10 @@ void main() { expect(find.text('Test Chip'), findsOneWidget); }); - + }); + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () { testWidgets('triggers onTap callback when tapped', (WidgetTester tester) async { bool tapped = false; @@ -72,30 +93,33 @@ void main() { expect(find.byIcon(Icons.close), findsOneWidget); }); - }); + testWidgets('ZetaChip changes selected property correctly', (WidgetTester tester) async { + bool selected = false; + StateSetter? setState; - testWidgets('ZetaChip changes selected property correctly', (WidgetTester tester) async { - bool selected = false; - StateSetter? setState; - - await tester.pumpWidget( - TestApp( - home: StatefulBuilder( - builder: (context, setState2) { - setState = setState2; - return ZetaChip(label: 'Chip', selected: selected); - }, + await tester.pumpWidget( + TestApp( + home: StatefulBuilder( + builder: (context, setState2) { + setState = setState2; + return ZetaChip(label: 'Chip', selected: selected); + }, + ), ), - ), - ); + ); - final Finder iconFinder = find.byIcon(ZetaIcons.check_mark_round); - expect(iconFinder, findsNothing); + final Finder iconFinder = find.byIcon(ZetaIcons.check_mark_round); + expect(iconFinder, findsNothing); - // Change isOpen property to true - setState?.call(() => selected = true); - await tester.pumpAndSettle(); + // Change isOpen property to true + setState?.call(() => selected = true); + await tester.pumpAndSettle(); - expect(iconFinder, findsOne); + expect(iconFinder, findsOne); + }); + }); + group('Golden Tests', () { + // goldenTest(goldenFile, widget, widgetType, 'PNG_FILE_NAME'); }); + group('Performance Tests', () {}); } diff --git a/test/src/components/comms_button/comms_button_test.dart b/test/src/components/comms_button/comms_button_test.dart index 0322682e..539792c0 100644 --- a/test/src/components/comms_button/comms_button_test.dart +++ b/test/src/components/comms_button/comms_button_test.dart @@ -1,19 +1,95 @@ -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'; void main() { - const goldenFile = GoldenFiles(component: 'comms_button'); + const String parentFolder = 'comms_button'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaCommsButton Tests', () { + group('Accessibility Tests', () { + testWidgets('Button meets accessibility requirements', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + const TestApp( + home: ZetaCommsButton( + label: 'Label', + semanticLabel: 'Phone', + icon: ZetaIcons.phone, + type: ZetaCommsButtonType.positive, + ), + ), + ); + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + handle.dispose(); + }); + + testWidgets('Button meets accessibility requirements when toggled', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + TestApp( + home: ZetaCommsButton( + label: 'Label', + semanticLabel: 'Phone', + icon: ZetaIcons.phone, + type: ZetaCommsButtonType.positive, + toggledLabel: 'Toggled Label', + toggledIcon: ZetaIcons.end_call, + toggledType: ZetaCommsButtonType.negative, + onToggle: (isToggled) {}, + ), + ), + ); + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + await tester.tap(find.byType(ZetaCommsButton)); + await tester.pumpAndSettle(); + + 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 = { + 'label': '"Label"', + 'onPressed': 'null', + 'onToggle': 'null', + 'toggledIcon': 'null', + 'toggledLabel': 'null', + 'toggleType': null, + 'focusNode': 'null', + 'semanticLabel': 'null', + 'type': 'positive', + 'size': 'medium', + 'icon': 'IconData(U+0E16B)', + }; + debugFillPropertiesTest( + const ZetaCommsButton( + label: 'Label', + icon: ZetaIcons.phone, + type: ZetaCommsButtonType.positive, + ), + debugFillProperties, + ); + testWidgets('Initializes with correct label', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( @@ -22,11 +98,6 @@ void main() { ); expect(find.text('Label'), findsOneWidget); - - await expectLater( - find.byType(ZetaCommsButton), - matchesGoldenFile(goldenFile.getFileUri('CommsButton_default')), - ); }); testWidgets('Initializes with correct icon', (WidgetTester tester) async { @@ -133,99 +204,22 @@ void main() { expect(pressed, isTrue); }); - - testWidgets('debugFillProperties Test', (WidgetTester tester) async { - final diagnostic = DiagnosticPropertiesBuilder(); - const ZetaCommsButton( - label: 'Label', - icon: ZetaIcons.phone, - type: ZetaCommsButtonType.positive, - ).debugFillProperties(diagnostic); - - expect(diagnostic.finder('label'), '"Label"'); - expect(diagnostic.finder('onPressed'), 'null'); - expect(diagnostic.finder('onToggle'), 'null'); - expect(diagnostic.finder('toggledIcon'), 'null'); - expect(diagnostic.finder('toggledLabel'), 'null'); - expect(diagnostic.finder('toggleType'), null); - expect(diagnostic.finder('focusNode'), 'null'); - expect(diagnostic.finder('semanticLabel'), 'null'); - expect(diagnostic.finder('type'), 'positive'); - expect(diagnostic.finder('size'), 'medium'); - expect(diagnostic.finder('icon'), 'IconData(U+0E16B)'); - }); - - testWidgets('Button meets accessibility requirements', (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget( - const TestApp( - home: ZetaCommsButton( - label: 'Label', - semanticLabel: 'Phone', - icon: ZetaIcons.phone, - type: ZetaCommsButtonType.positive, - ), - ), - ); - await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); - await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - await expectLater(tester, meetsGuideline(textContrastGuideline)); - - handle.dispose(); - }); - - testWidgets('Button meets accessibility requirements when toggled', (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget( - TestApp( - home: ZetaCommsButton( - label: 'Label', - semanticLabel: 'Phone', - icon: ZetaIcons.phone, - type: ZetaCommsButtonType.positive, - toggledLabel: 'Toggled Label', - toggledIcon: ZetaIcons.end_call, - toggledType: ZetaCommsButtonType.negative, - onToggle: (isToggled) {}, - ), - ), - ); - await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); - await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - await expectLater(tester, meetsGuideline(textContrastGuideline)); - - await tester.tap(find.byType(ZetaCommsButton)); - await tester.pumpAndSettle(); - - await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); - await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); - await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); - await expectLater(tester, meetsGuideline(textContrastGuideline)); - - handle.dispose(); - }); }); - - group('ZetaCommsButton Golden Tests', () { + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { for (final type in ZetaCommsButtonType.values) { - testWidgets('ZetaCommsButton with type $type', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaCommsButton( - label: 'Label', - icon: ZetaIcons.phone, - type: type, - ), - ), - ); - - await expectLater( - find.byType(ZetaCommsButton), - matchesGoldenFile(goldenFile.getFileUri('CommsButton_${type.name}')), - ); - }); + goldenTest( + goldenFile, + ZetaCommsButton( + label: 'Label', + icon: ZetaIcons.phone, + type: type, + ), + 'CommsButton_${type.name}', + ); } }); + group('Performance Tests', () {}); } diff --git a/test/src/components/comms_button/golden/CommsButton_default.png b/test/src/components/comms_button/golden/CommsButton_default.png deleted file mode 100644 index 9cc11e3b..00000000 Binary files a/test/src/components/comms_button/golden/CommsButton_default.png and /dev/null differ diff --git a/test/src/components/dialpad/dialpad_test.dart b/test/src/components/dialpad/dialpad_test.dart index 9909f73d..005f3733 100644 --- a/test/src/components/dialpad/dialpad_test.dart +++ b/test/src/components/dialpad/dialpad_test.dart @@ -1,6 +1,5 @@ import 'dart:ui'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -10,13 +9,64 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'dialpad'); + const String parentFolder = 'dialpad'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaDialPad Tests', () { + group('Accessibility Tests', () {}); + + group('Content Tests', () { + final debugFillProperties = { + 'onNumber': 'null', + 'onText': 'null', + 'buttonsPerRow': '3', + 'buttonValues': '{1: , 2: ABC, 3: DEF, 4: GHI, 5: JKL, 6: MNO, 7: PQRS, 8: TUV, 9: WXYZ, *: , 0: +, #: }', + }; + debugFillPropertiesTest( + const ZetaDialPad(), + debugFillProperties, + ); + + final debugFillPropertiesSingleButton = { + 'primary': '"1"', + 'secondary': '""', + 'onTap': 'null', + 'topPadding': '3.0', + }; + debugFillPropertiesTest( + const ZetaDialPadButton(primary: '1'), + debugFillPropertiesSingleButton, + ); + }); + + group('Dimensions Tests', () {}); + + group('Styling Tests', () { + testWidgets('Hover styles for button are correct', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + screenSize: Size(1000, 1000), + home: ZetaDialPadButton(primary: '1'), + ), + ); + final buttonFinder = find.byType(ZetaDialPadButton); + final inkwellFinder = find.byType(InkWell); + final InkWell inkWell = tester.firstWidget(inkwellFinder); + + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + await tester.pump(); + await gesture.moveTo(tester.getCenter(buttonFinder)); + await tester.pumpAndSettle(); + + expect(inkWell.overlayColor?.resolve({WidgetState.hovered}), const ZetaPrimitivesLight().cool.shade20); + }); + }); + + group('Interaction Tests', () { testWidgets('Initializes with correct parameters and is enabled', (WidgetTester tester) async { String number = ''; String text = ''; @@ -123,163 +173,130 @@ void main() { /// Allow all timers to end in text debounce await debounceWait(); + }); + + testWidgets('Initializes with correct parameters and is disabled', (WidgetTester tester) async { + const String number = ''; + const String text = ''; - await expectLater( - dialPadFinder, - matchesGoldenFile(goldenFile.getFileUri('dialpad_enabled')), + Future debounceWait() => tester.binding.delayed(const Duration(milliseconds: 500)); + + await tester.pumpWidget( + const TestApp( + screenSize: Size(1000, 1000), + home: ZetaDialPad(), + ), ); - }); - }); - testWidgets('Initializes with correct parameters and is disabled', (WidgetTester tester) async { - const String number = ''; - const String text = ''; + final dialPadFinder = find.byType(ZetaDialPad); + final buttonFinder = find.byType(ZetaDialPadButton); + + final oneFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '1'); + final twoFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '2'); + final threeFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '3'); + final starFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '*'); + final hashFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '#'); - Future debounceWait() => tester.binding.delayed(const Duration(milliseconds: 500)); + final ZetaDialPad dialPad = tester.firstWidget(dialPadFinder); + final List dialPadButtons = tester.widgetList(buttonFinder).toList(); - await tester.pumpWidget( - const TestApp( - screenSize: Size(1000, 1000), - home: ZetaDialPad(), - ), - ); - final dialPadFinder = find.byType(ZetaDialPad); - final buttonFinder = find.byType(ZetaDialPadButton); - - final oneFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '1'); - final twoFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '2'); - final threeFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '3'); - final starFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '*'); - final hashFinder = find.byWidgetPredicate((widget) => widget is ZetaDialPadButton && widget.primary == '#'); - - final ZetaDialPad dialPad = tester.firstWidget(dialPadFinder); - final List dialPadButtons = tester.widgetList(buttonFinder).toList(); - - final ZetaDialPadButton one = tester.firstWidget(oneFinder); - final ZetaDialPadButton two = tester.firstWidget(twoFinder); - final ZetaDialPadButton three = tester.firstWidget(threeFinder); - - /// Dial Pad built correctly. - expect(dialPad.buttonsPerRow, 3); - expect(dialPadButtons.length, 12); - - /// Dial Pad buttons built correctly. - expect(one.primary, '1'); - expect(one.secondary, ''); - expect(two.primary, '2'); - expect(two.secondary, 'ABC'); - expect(three.primary, '3'); - expect(three.secondary, 'DEF'); - - /// Tap button with only number. - await tester.tap(oneFinder); - await tester.pump(); - expect(number, ''); - - /// Tap button with number and text. - await tester.tap(twoFinder); - await tester.pump(); - await debounceWait(); - expect(number, ''); - expect(text, ''); - - /// Tap different button. - await tester.tap(threeFinder); - await tester.pump(); - await debounceWait(); - expect(number, ''); - expect(text, ''); - - /// Tap text button twice. - await tester.tap(twoFinder); - await tester.tap(twoFinder); - await tester.pump(); - await debounceWait(); - expect(text, ''); - - /// Tap text button thrice. - await tester.tap(twoFinder); - await tester.tap(twoFinder); - await tester.tap(twoFinder); - await tester.pump(); - await debounceWait(); - expect(text, ''); - - /// Tap different text buttons to ensure debounce is cancelled. - await tester.tap(twoFinder); - await tester.tap(threeFinder); - await tester.tap(twoFinder); - await tester.pump(); - await debounceWait(); - expect(text, ''); - - /// Tap text button more times than there is options to ensure it loops around correctly. - await tester.tap(threeFinder); - await tester.tap(threeFinder); - await tester.tap(threeFinder); - await tester.tap(threeFinder); - await tester.tap(threeFinder); - await tester.tap(threeFinder); - await tester.tap(oneFinder); - await tester.pump(); - expect(text, ''); - - /// Tap buttons with symbols - await tester.ensureVisible(starFinder); - await tester.tap(starFinder); - await tester.tap(hashFinder); - await tester.pump(); - expect(number, ''); - - /// Allow all timers to end in text debounce - await debounceWait(); - - await expectLater( - dialPadFinder, - matchesGoldenFile(goldenFile.getFileUri('dialpad_disabled')), - ); - }); - testWidgets('ZetaDialPadButton interaction', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - screenSize: Size(1000, 1000), - home: ZetaDialPadButton(primary: '1'), - ), - ); - final buttonFinder = find.byType(ZetaDialPadButton); - final inkwellFinder = find.byType(InkWell); - final InkWell inkWell = tester.firstWidget(inkwellFinder); + final ZetaDialPadButton one = tester.firstWidget(oneFinder); + final ZetaDialPadButton two = tester.firstWidget(twoFinder); + final ZetaDialPadButton three = tester.firstWidget(threeFinder); - final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - await gesture.addPointer(location: Offset.zero); - await tester.pump(); - await gesture.moveTo(tester.getCenter(buttonFinder)); - await tester.pumpAndSettle(); + /// Dial Pad built correctly. + expect(dialPad.buttonsPerRow, 3); + expect(dialPadButtons.length, 12); - expect(inkWell.overlayColor?.resolve({WidgetState.hovered}), const ZetaPrimitivesLight().cool.shade20); + /// Dial Pad buttons built correctly. + expect(one.primary, '1'); + expect(one.secondary, ''); + expect(two.primary, '2'); + expect(two.secondary, 'ABC'); + expect(three.primary, '3'); + expect(three.secondary, 'DEF'); - await expectLater( - buttonFinder, - matchesGoldenFile(goldenFile.getFileUri('dialpadbutton')), - ); - }); + /// Tap button with only number. + await tester.tap(oneFinder); + await tester.pump(); + expect(number, ''); - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaDialPad().debugFillProperties(diagnostics); + /// Tap button with number and text. + await tester.tap(twoFinder); + await tester.pump(); + await debounceWait(); + expect(number, ''); + expect(text, ''); - expect(diagnostics.finder('onNumber'), 'null'); - expect(diagnostics.finder('onText'), 'null'); - expect(diagnostics.finder('buttonsPerRow'), '3'); - expect( - diagnostics.finder('buttonValues'), - '{1: , 2: ABC, 3: DEF, 4: GHI, 5: JKL, 6: MNO, 7: PQRS, 8: TUV, 9: WXYZ, *: , 0: +, #: }', - ); + /// Tap different button. + await tester.tap(threeFinder); + await tester.pump(); + await debounceWait(); + expect(number, ''); + expect(text, ''); + + /// Tap text button twice. + await tester.tap(twoFinder); + await tester.tap(twoFinder); + await tester.pump(); + await debounceWait(); + expect(text, ''); + + /// Tap text button thrice. + await tester.tap(twoFinder); + await tester.tap(twoFinder); + await tester.tap(twoFinder); + await tester.pump(); + await debounceWait(); + expect(text, ''); + + /// Tap different text buttons to ensure debounce is cancelled. + await tester.tap(twoFinder); + await tester.tap(threeFinder); + await tester.tap(twoFinder); + await tester.pump(); + await debounceWait(); + expect(text, ''); + + /// Tap text button more times than there is options to ensure it loops around correctly. + await tester.tap(threeFinder); + await tester.tap(threeFinder); + await tester.tap(threeFinder); + await tester.tap(threeFinder); + await tester.tap(threeFinder); + await tester.tap(threeFinder); + await tester.tap(oneFinder); + await tester.pump(); + expect(text, ''); + + /// Tap buttons with symbols + await tester.ensureVisible(starFinder); + await tester.tap(starFinder); + await tester.tap(hashFinder); + await tester.pump(); + expect(number, ''); + + /// Allow all timers to end in text debounce + await debounceWait(); + }); + }); - final internalDiagnostics = DiagnosticPropertiesBuilder(); - const ZetaDialPadButton(primary: '1').debugFillProperties(internalDiagnostics); - expect(internalDiagnostics.finder('primary'), '"1"'); - expect(internalDiagnostics.finder('secondary'), '""'); - expect(internalDiagnostics.finder('onTap'), 'null'); - expect(internalDiagnostics.finder('topPadding'), '3.0'); + group('Golden Tests', () { + goldenTest( + goldenFile, + const ZetaDialPad( + onNumber: print, + onText: print, + ), + 'dialpad_enabled', + screenSize: const Size(1000, 1000), + ); + goldenTest( + goldenFile, + const ZetaDialPad(), + 'dialpad_disabled', + screenSize: const Size(1000, 1000), + ); }); + + group('Performance Tests', () {}); } diff --git a/test/src/components/dialpad/golden/dialpad_disabled.png b/test/src/components/dialpad/golden/dialpad_disabled.png index 4b29ffa0..469b0381 100644 Binary files a/test/src/components/dialpad/golden/dialpad_disabled.png and b/test/src/components/dialpad/golden/dialpad_disabled.png differ diff --git a/test/src/components/dialpad/golden/dialpad_enabled.png b/test/src/components/dialpad/golden/dialpad_enabled.png index 4b29ffa0..469b0381 100644 Binary files a/test/src/components/dialpad/golden/dialpad_enabled.png and b/test/src/components/dialpad/golden/dialpad_enabled.png differ diff --git a/test/src/components/fab/fab_test.dart b/test/src/components/fab/fab_test.dart new file mode 100644 index 00000000..4058ffc3 --- /dev/null +++ b/test/src/components/fab/fab_test.dart @@ -0,0 +1,267 @@ +import 'dart:ui'; + +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'; + +void main() { + const String parentFolder = 'fab'; + + const goldenFile = GoldenFiles(component: parentFolder); + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('Accessibility Tests', () {}); + + group('Content Tests', () { + final debugFillProperties = { + 'label': 'null', + 'onPressed': 'null', + 'type': 'primary', + 'size': 'small', + 'shape': 'full', + 'icon': 'IconData(U+0E009)', + 'initiallyExpanded': 'false', + 'focusNode': 'null', + }; + debugFillPropertiesTest( + const ZetaFAB(), + debugFillProperties, + ); + + testWidgets('Initializes with correct parameters', (WidgetTester tester) async { + final scrollController = ScrollController(); + await tester.pumpWidget( + TestApp( + home: ZetaFAB(scrollController: scrollController, label: 'Label', onPressed: () {}), + ), + ); + + expect(find.byType(ZetaFAB), findsOneWidget); + }); + + testWidgets('Icon Test', (WidgetTester tester) async { + final scrollController = ScrollController(); + await tester.pumpWidget( + TestApp( + home: ZetaFAB( + scrollController: scrollController, + onPressed: () {}, + type: ZetaFabType.inverse, + shape: ZetaWidgetBorder.rounded, + size: ZetaFabSize.large, + ), + ), + ); + expect(find.byIcon(ZetaIcons.add_round), findsOneWidget); + final fabFinder = find.byType(ZetaFAB); + final ZetaFAB fab = tester.firstWidget(fabFinder); + + expect(fab.expanded, false); + expect(fab.type, ZetaFabType.inverse); + expect(fab.shape, ZetaWidgetBorder.rounded); + }); + + testWidgets('Expanded', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaFAB( + expanded: true, + onPressed: () {}, + label: 'Label', + type: ZetaFabType.secondary, + shape: ZetaWidgetBorder.sharp, + ), + ), + ); + + final fabFinder = find.byType(ZetaFAB); + final ZetaFAB fab = tester.firstWidget(fabFinder); + + expect(fab.expanded, true); + expect(fab.type, ZetaFabType.secondary); + expect(fab.shape, ZetaWidgetBorder.sharp); + }); + + testWidgets('Disabled FAB', (WidgetTester tester) async { + final scrollController = ScrollController(); + await tester.pumpWidget( + TestApp( + home: ZetaFAB(scrollController: scrollController, label: 'Disabled'), + ), + ); + + final fabFinder = find.byType(ZetaFAB); + final ZetaFAB fab = tester.firstWidget(fabFinder); + + expect(fab.onPressed, isNull); + expect(fab.type, ZetaFabType.primary); + expect(fab.shape, ZetaWidgetBorder.full); + }); + + testWidgets('Label is correct', (WidgetTester tester) async { + final scrollController = ScrollController(); + StateSetter? setState; + bool expanded = false; + + await tester.pumpWidget( + TestApp( + home: StatefulBuilder( + builder: (context, setState2) { + setState = setState2; + return ZetaFAB( + scrollController: scrollController, + expanded: expanded, + label: 'Label', + onPressed: () {}, + ); + }, + ), + ), + ); + + final labelFinder = find.text('Label'); + + expect(labelFinder, findsOne); + + setState?.call(() => expanded = true); + + await tester.pumpAndSettle(); + expect(labelFinder, findsOne); + }); + }); + + group('Dimensions Tests', () {}); + + group('Styling Tests', () { + testWidgets('hover colours are correct', (WidgetTester tester) async { + final FocusNode node = FocusNode(); + + await tester.pumpWidget( + TestApp( + home: ZetaFAB( + expanded: true, + onPressed: () {}, + label: 'Label', + type: ZetaFabType.secondary, + shape: ZetaWidgetBorder.sharp, + focusNode: node, + ), + ), + ); + + final fabFinder = find.byType(ZetaFAB); + final ZetaFAB fab = tester.firstWidget(fabFinder); + final filledButtonFinder = find.byType(FilledButton); + final FilledButton filledButton = tester.firstWidget(filledButtonFinder); + + expect(fab.expanded, true); + expect(fab.type, ZetaFabType.secondary); + expect(fab.shape, ZetaWidgetBorder.sharp); + + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: Offset.zero); + await tester.pumpAndSettle(); + await gesture.moveTo(tester.getCenter(fabFinder)); + await tester.pumpAndSettle(); + + expect( + filledButton.style?.backgroundColor?.resolve({WidgetState.hovered}), + const ZetaPrimitivesLight().yellow.shade30, + ); + + await gesture.moveTo(Offset.zero); + await tester.pumpAndSettle(); + + node.requestFocus(); + await tester.pumpAndSettle(); + expect( + filledButton.style?.side?.resolve({WidgetState.focused}), + BorderSide(color: const ZetaPrimitivesLight().blue[50]!, width: ZetaBorders.medium), + ); + }); + }); + + group('Interaction Tests', () { + testWidgets('OnPressed callback', (WidgetTester tester) async { + bool isPressed = false; + final scrollController = ScrollController(); + + await tester.pumpWidget( + TestApp( + home: ZetaFAB(scrollController: scrollController, label: 'Label', onPressed: () => isPressed = true), + ), + ); + final TestGesture e = await tester.press(find.byType(ZetaFAB)); + + await tester.pumpAndSettle(); + + await e.up(); + expect(isPressed, isTrue); + }); + }); + + group('Golden Tests', () { + goldenTest( + goldenFile, + ZetaFAB( + scrollController: ScrollController(), + label: 'Label', + onPressed: () {}, + ), + 'FAB_default', + ); + goldenTest( + goldenFile, + ZetaFAB(scrollController: ScrollController(), label: 'Label', onPressed: () => {}), + 'FAB_pressed', + beforeComparison: (tester) async { + await tester.press(find.byType(ZetaFAB)); + await tester.pumpAndSettle(); + }, + ); + goldenTest( + goldenFile, + ZetaFAB( + scrollController: ScrollController(), + onPressed: () {}, + type: ZetaFabType.inverse, + shape: ZetaWidgetBorder.rounded, + size: ZetaFabSize.large, + ), + 'FAB_inverse', + ); + goldenTest( + goldenFile, + ZetaFAB(scrollController: ScrollController(), label: 'Label', onPressed: () => {}), + 'FAB_pressed', + beforeComparison: (tester) async { + await tester.press(find.byType(ZetaFAB)); + await tester.pumpAndSettle(); + }, + ); + goldenTest( + goldenFile, + ZetaFAB( + expanded: true, + onPressed: () {}, + label: 'Label', + type: ZetaFabType.secondary, + shape: ZetaWidgetBorder.sharp, + ), + 'FAB_secondary', + ); + goldenTest( + goldenFile, + ZetaFAB(scrollController: ScrollController(), label: 'Disabled'), + 'FAB_disabled', + ); + }); + + group('Performance Tests', () {}); +} diff --git a/test/src/components/fab/golden/FAB_default.png b/test/src/components/fab/golden/FAB_default.png index a2c21f08..b61dbfe3 100644 Binary files a/test/src/components/fab/golden/FAB_default.png and b/test/src/components/fab/golden/FAB_default.png differ diff --git a/test/src/components/fab/golden/FAB_disabled.png b/test/src/components/fab/golden/FAB_disabled.png index 592c965c..72fda84a 100644 Binary files a/test/src/components/fab/golden/FAB_disabled.png and b/test/src/components/fab/golden/FAB_disabled.png differ diff --git a/test/src/components/fab/golden/FAB_inverse.png b/test/src/components/fab/golden/FAB_inverse.png index 89ae4dc9..cfd21496 100644 Binary files a/test/src/components/fab/golden/FAB_inverse.png and b/test/src/components/fab/golden/FAB_inverse.png differ diff --git a/test/src/components/fab/golden/FAB_pressed.png b/test/src/components/fab/golden/FAB_pressed.png index f252b620..5831c5f1 100644 Binary files a/test/src/components/fab/golden/FAB_pressed.png and b/test/src/components/fab/golden/FAB_pressed.png differ diff --git a/test/src/components/fab/golden/FAB_secondary.png b/test/src/components/fab/golden/FAB_secondary.png index 2efcda2d..eef87feb 100644 Binary files a/test/src/components/fab/golden/FAB_secondary.png and b/test/src/components/fab/golden/FAB_secondary.png differ diff --git a/test/src/components/fabs/fab_test.dart b/test/src/components/fabs/fab_test.dart deleted file mode 100644 index 1152f2aa..00000000 --- a/test/src/components/fabs/fab_test.dart +++ /dev/null @@ -1,222 +0,0 @@ -import 'dart:ui'; - -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'; - -void main() { - const goldenFile = GoldenFiles(component: 'fab'); - - setUpAll(() { - goldenFileComparator = TolerantComparator(goldenFile.uri); - }); - - group('ZetaFAB Tests', () { - testWidgets('Initializes with correct parameters', (WidgetTester tester) async { - final scrollController = ScrollController(); - await tester.pumpWidget( - TestApp( - home: ZetaFAB(scrollController: scrollController, label: 'Label', onPressed: () {}), - ), - ); - - expect(find.byType(ZetaFAB), findsOneWidget); - - await expectLater( - find.byType(ZetaFAB), - matchesGoldenFile(goldenFile.getFileUri('FAB_default')), - ); - }); - - testWidgets('OnPressed callback', (WidgetTester tester) async { - bool isPressed = false; - final scrollController = ScrollController(); - - await tester.pumpWidget( - TestApp( - home: ZetaFAB(scrollController: scrollController, label: 'Label', onPressed: () => isPressed = true), - ), - ); - final TestGesture e = await tester.press(find.byType(ZetaFAB)); - - await tester.pumpAndSettle(); - - await expectLater( - find.byType(ZetaFAB), - matchesGoldenFile(goldenFile.getFileUri('FAB_pressed')), - ); - - await e.up(); - expect(isPressed, isTrue); - }); - }); - - testWidgets('Icon Test', (WidgetTester tester) async { - final scrollController = ScrollController(); - await tester.pumpWidget( - TestApp( - home: ZetaFAB( - scrollController: scrollController, - onPressed: () {}, - type: ZetaFabType.inverse, - shape: ZetaWidgetBorder.rounded, - size: ZetaFabSize.large, - ), - ), - ); - expect(find.byIcon(ZetaIcons.add_round), findsOneWidget); - final fabFinder = find.byType(ZetaFAB); - final ZetaFAB fab = tester.firstWidget(fabFinder); - - expect(fab.expanded, false); - expect(fab.type, ZetaFabType.inverse); - expect(fab.shape, ZetaWidgetBorder.rounded); - - await expectLater( - find.byType(ZetaFAB), - matchesGoldenFile(goldenFile.getFileUri('FAB_inverse')), - ); - }); - - testWidgets('Expanded', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaFAB( - expanded: true, - onPressed: () {}, - label: 'Label', - type: ZetaFabType.secondary, - shape: ZetaWidgetBorder.sharp, - ), - ), - ); - - final fabFinder = find.byType(ZetaFAB); - final ZetaFAB fab = tester.firstWidget(fabFinder); - - expect(fab.expanded, true); - expect(fab.type, ZetaFabType.secondary); - expect(fab.shape, ZetaWidgetBorder.sharp); - - await expectLater( - find.byType(ZetaFAB), - matchesGoldenFile(goldenFile.getFileUri('FAB_secondary')), - ); - }); - testWidgets('ZetaFAB interactive', (WidgetTester tester) async { - final FocusNode node = FocusNode(); - - await tester.pumpWidget( - TestApp( - home: ZetaFAB( - expanded: true, - onPressed: () {}, - label: 'Label', - type: ZetaFabType.secondary, - shape: ZetaWidgetBorder.sharp, - focusNode: node, - ), - ), - ); - - final fabFinder = find.byType(ZetaFAB); - final ZetaFAB fab = tester.firstWidget(fabFinder); - final filledButtonFinder = find.byType(FilledButton); - final FilledButton filledButton = tester.firstWidget(filledButtonFinder); - - expect(fab.expanded, true); - expect(fab.type, ZetaFabType.secondary); - expect(fab.shape, ZetaWidgetBorder.sharp); - - final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - await gesture.addPointer(location: Offset.zero); - await tester.pumpAndSettle(); - await gesture.moveTo(tester.getCenter(fabFinder)); - await tester.pumpAndSettle(); - - expect( - filledButton.style?.backgroundColor?.resolve({WidgetState.hovered}), - const ZetaPrimitivesLight().yellow.shade30, - ); - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - - node.requestFocus(); - await tester.pumpAndSettle(); - expect( - filledButton.style?.side?.resolve({WidgetState.focused}), - BorderSide(color: const ZetaPrimitivesLight().blue.shade50, width: ZetaBorders.medium), - ); - }); - - testWidgets('Disabled FAB', (WidgetTester tester) async { - final scrollController = ScrollController(); - await tester.pumpWidget( - TestApp( - home: ZetaFAB(scrollController: scrollController, label: 'Disabled'), - ), - ); - - final fabFinder = find.byType(ZetaFAB); - final ZetaFAB fab = tester.firstWidget(fabFinder); - - expect(fab.onPressed, isNull); - expect(fab.type, ZetaFabType.primary); - expect(fab.shape, ZetaWidgetBorder.full); - - await expectLater( - find.byType(ZetaFAB), - matchesGoldenFile(goldenFile.getFileUri('FAB_disabled')), - ); - }); - - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaFAB().debugFillProperties(diagnostics); - - expect(diagnostics.finder('label'), 'null'); - expect(diagnostics.finder('onPressed'), 'null'); - expect(diagnostics.finder('type'), 'primary'); - expect(diagnostics.finder('size'), 'small'); - expect(diagnostics.finder('shape'), 'full'); - expect(diagnostics.finder('icon'), 'IconData(U+0E009)'); - expect(diagnostics.finder('initiallyExpanded'), 'false'); - expect(diagnostics.finder('focusNode'), 'null'); - }); - - testWidgets('Label is correct', (WidgetTester tester) async { - final scrollController = ScrollController(); - StateSetter? setState; - bool expanded = false; - - await tester.pumpWidget( - TestApp( - home: StatefulBuilder( - builder: (context, setState2) { - setState = setState2; - return ZetaFAB( - scrollController: scrollController, - expanded: expanded, - label: 'Label', - onPressed: () {}, - ); - }, - ), - ), - ); - - final labelFinder = find.text('Label'); - - expect(labelFinder, findsOne); - - setState?.call(() => expanded = true); - - await tester.pumpAndSettle(); - expect(labelFinder, findsOne); - }); -} diff --git a/test/src/components/fabs/golden/FAB_default.png b/test/src/components/fabs/golden/FAB_default.png deleted file mode 100644 index 8ab4dac2..00000000 Binary files a/test/src/components/fabs/golden/FAB_default.png and /dev/null differ diff --git a/test/src/components/fabs/golden/FAB_pressed.png b/test/src/components/fabs/golden/FAB_pressed.png deleted file mode 100644 index 6be7b8db..00000000 Binary files a/test/src/components/fabs/golden/FAB_pressed.png and /dev/null differ diff --git a/test/src/components/icon/icon_test.dart b/test/src/components/icon/icon_test.dart index 05cbbb5e..3afa42bd 100644 --- a/test/src/components/icon/icon_test.dart +++ b/test/src/components/icon/icon_test.dart @@ -1,28 +1,60 @@ -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'; void main() { - group('Zeta Icon', () { - testWidgets('renders icon correctly', (WidgetTester tester) async { - await tester.pumpWidget(const TestApp(home: ZetaIcon(ZetaIcons.add_round))); + const String parentFolder = 'icon'; + + const goldenFile = GoldenFiles(component: parentFolder); + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('Accessibility Tests', () { + testWidgets('applies correct semantic label to icon', (WidgetTester tester) async { + const String semanticLabel = 'Add Icon'; + await tester.pumpWidget(const TestApp(home: ZetaIcon(ZetaIcons.add_round, semanticLabel: semanticLabel))); final iconFinder = find.byIcon(ZetaIcons.add_round); - expect(iconFinder, findsOneWidget); + final iconWidget = tester.widget(iconFinder); + expect(iconWidget.semanticLabel, equals(semanticLabel)); }); + }); + group('Content Tests', () { + final debugFillProperties = { + 'icon': 'IconData(U+0E045)', + 'rounded': 'false', + 'size': '10.0', + 'fill': 'null', + 'weight': 'null', + 'grade': 'null', + 'opticalSize': 'null', + 'color': 'null', + 'shadows': 'null', + 'semanticLabel': '"Cached"', + 'textDirection': 'null', + 'applyTextScaling': 'null', + }; + debugFillPropertiesTest( + const ZetaIcon( + ZetaIcons.cached, + rounded: false, + size: 10, + semanticLabel: 'Cached', + ), + debugFillProperties, + ); - testWidgets('applies correct size to icon', (WidgetTester tester) async { - const double iconSize = 24; - await tester.pumpWidget(const TestApp(home: ZetaIcon(ZetaIcons.add_round, size: iconSize))); + testWidgets('renders icon correctly', (WidgetTester tester) async { + await tester.pumpWidget(const TestApp(home: ZetaIcon(ZetaIcons.add_round))); final iconFinder = find.byIcon(ZetaIcons.add_round); - final iconWidget = tester.widget(iconFinder); - expect(iconWidget.size, equals(iconSize)); + expect(iconFinder, findsOneWidget); }); - testWidgets('applies correct color to icon', (WidgetTester tester) async { + testWidgets('color value is correct', (WidgetTester tester) async { const Color iconColor = Colors.red; await tester.pumpWidget(const TestApp(home: ZetaIcon(ZetaIcons.add_round, color: iconColor))); final iconFinder = find.byIcon(ZetaIcons.add_round); @@ -30,14 +62,6 @@ void main() { expect(iconWidget.color, equals(iconColor)); }); - testWidgets('applies correct semantic label to icon', (WidgetTester tester) async { - const String semanticLabel = 'Add Icon'; - await tester.pumpWidget(const TestApp(home: ZetaIcon(ZetaIcons.add_round, semanticLabel: semanticLabel))); - final iconFinder = find.byIcon(ZetaIcons.add_round); - final iconWidget = tester.widget(iconFinder); - expect(iconWidget.semanticLabel, equals(semanticLabel)); - }); - testWidgets('applies sharp family to icon', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( @@ -58,7 +82,18 @@ void main() { expect(iconFinderRound, findsOneWidget); expect(iconFinderSharp, findsExactly(1)); }); - + }); + group('Dimensions Tests', () { + testWidgets('applies correct size to icon', (WidgetTester tester) async { + const double iconSize = 24; + await tester.pumpWidget(const TestApp(home: ZetaIcon(ZetaIcons.add_round, size: iconSize))); + final iconFinder = find.byIcon(ZetaIcons.add_round); + final sizeOfIcon = tester.getSize(iconFinder); + expect(sizeOfIcon.width, equals(iconSize)); + expect(sizeOfIcon.height, equals(iconSize)); + }); + }); + group('Styling Tests', () { testWidgets('applies correct font family to icon', (WidgetTester tester) async { await tester.pumpWidget(const TestApp(home: ZetaIcon(ZetaIcons.add_round))); final iconFinder = find.byIcon(ZetaIcons.add_round); @@ -145,28 +180,10 @@ void main() { final iconWidget = tester.widget(iconFinder); expect(iconWidget.icon?.fontFamily, equals('MaterialIcons')); }); - - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaIcon( - ZetaIcons.cached, - rounded: false, - size: 10, - semanticLabel: 'Cached', - ).debugFillProperties(diagnostics); - - expect(diagnostics.finder('icon'), 'IconData(U+0E045)'); - expect(diagnostics.finder('rounded'), 'false'); - expect(diagnostics.finder('size'), '10.0'); - expect(diagnostics.finder('fill'), 'null'); - expect(diagnostics.finder('weight'), 'null'); - expect(diagnostics.finder('grade'), 'null'); - expect(diagnostics.finder('opticalSize'), 'null'); - expect(diagnostics.finder('color'), 'null'); - expect(diagnostics.finder('shadows'), 'null'); - expect(diagnostics.finder('semanticLabel'), '"Cached"'); - expect(diagnostics.finder('textDirection'), 'null'); - expect(diagnostics.finder('applyTextScaling'), 'null'); - }); }); + group('Interaction Tests', () {}); + group('Golden Tests', () { + // goldenTest(goldenFile, widget, widgetType, 'PNG_FILE_NAME'); + }); + group('Performance Tests', () {}); } diff --git a/test/src/components/in_page_banner/golden/in_page_banner_buttons.png b/test/src/components/in_page_banner/golden/in_page_banner_buttons.png index e8a0f1b7..5aab9b2c 100644 Binary files a/test/src/components/in_page_banner/golden/in_page_banner_buttons.png and b/test/src/components/in_page_banner/golden/in_page_banner_buttons.png differ diff --git a/test/src/components/in_page_banner/golden/in_page_banner_default.png b/test/src/components/in_page_banner/golden/in_page_banner_default.png index 4bc80c6d..695a9b77 100644 Binary files a/test/src/components/in_page_banner/golden/in_page_banner_default.png and b/test/src/components/in_page_banner/golden/in_page_banner_default.png differ diff --git a/test/src/components/in_page_banner/golden/in_page_banner_negative.png b/test/src/components/in_page_banner/golden/in_page_banner_negative.png index 02baa758..5d5e7910 100644 Binary files a/test/src/components/in_page_banner/golden/in_page_banner_negative.png and b/test/src/components/in_page_banner/golden/in_page_banner_negative.png differ diff --git a/test/src/components/in_page_banner/golden/in_page_banner_positive.png b/test/src/components/in_page_banner/golden/in_page_banner_positive.png index 1880a354..ce471b3c 100644 Binary files a/test/src/components/in_page_banner/golden/in_page_banner_positive.png and b/test/src/components/in_page_banner/golden/in_page_banner_positive.png differ diff --git a/test/src/components/in_page_banner/in_page_banner_test.dart b/test/src/components/in_page_banner/in_page_banner_test.dart index ca47a592..e6275999 100644 --- a/test/src/components/in_page_banner/in_page_banner_test.dart +++ b/test/src/components/in_page_banner/in_page_banner_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -8,120 +7,170 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'in_page_banner'); + const String parentFolder = 'in_page_banner'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaInPageBanner Tests', () { - testWidgets('ZetaInPageBanner creation', (WidgetTester tester) async { + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'onClose': 'null', + 'status': 'info', + 'title': 'null', + 'customIcon': 'null', + }; + debugFillPropertiesTest( + const ZetaInPageBanner(content: Placeholder()), + debugFillProperties, + ); + + testWidgets('renders correct icon and text', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( home: ZetaInPageBanner(content: Text('Test'), title: 'Title'), ), ); - final Finder decoratedBoxFinder = find.byType(DecoratedBox); - final DecoratedBox box = tester.firstWidget(decoratedBoxFinder); - expect(find.byType(ZetaInPageBanner), findsOneWidget); expect(find.text('Test'), findsOneWidget); + }); + + testWidgets("ZetaInPageBanner shows 'close icon' correctly", (WidgetTester tester) async { + await tester.pumpWidget( + TestApp(home: ZetaInPageBanner(content: const Text('Test'), onClose: () {}, status: ZetaWidgetStatus.negative)), + ); + + expect(find.byIcon(ZetaIcons.close_round), findsOneWidget); + }); + + testWidgets("ZetaInPageBanner hides 'close icon' correctly", (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp(home: ZetaInPageBanner(content: Text('Test'), status: ZetaWidgetStatus.positive)), + ); + expect(find.byIcon(ZetaIcons.close_round), findsNothing); + }); + }); + group('Dimensions Tests', () {}); + group('Styling Tests', () { + testWidgets('default background colour is correct', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaInPageBanner(content: Text('Test'), title: 'Title'), + ), + ); + final Finder decoratedBoxFinder = find.byType(DecoratedBox); + final DecoratedBox box = tester.firstWidget(decoratedBoxFinder); expect(box.decoration.runtimeType, BoxDecoration); final BoxDecoration decoration = box.decoration as BoxDecoration; expect(decoration.color, const ZetaPrimitivesLight().purple.shade10); + }); - await expectLater( - find.byType(ZetaInPageBanner), - matchesGoldenFile(goldenFile.getFileUri('in_page_banner_default')), + testWidgets('negative background colour is correct', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp(home: ZetaInPageBanner(content: const Text('Test'), onClose: () {}, status: ZetaWidgetStatus.negative)), ); + + final Finder decoratedBoxFinder = find.byType(DecoratedBox); + final DecoratedBox box = tester.firstWidget(decoratedBoxFinder); + expect(box.decoration.runtimeType, BoxDecoration); + final BoxDecoration decoration = box.decoration as BoxDecoration; + expect(decoration.color, const ZetaPrimitivesLight().red.shade10); }); - }); - testWidgets("ZetaInPageBanner shows 'close icon' correctly", (WidgetTester tester) async { - await tester.pumpWidget( - TestApp(home: ZetaInPageBanner(content: const Text('Test'), onClose: () {}, status: ZetaWidgetStatus.negative)), - ); + testWidgets('positive background colour is correct', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp(home: ZetaInPageBanner(content: Text('Test'), status: ZetaWidgetStatus.positive)), + ); - expect(find.byIcon(ZetaIcons.close_round), findsOneWidget); - final Finder decoratedBoxFinder = find.byType(DecoratedBox); - final DecoratedBox box = tester.firstWidget(decoratedBoxFinder); - expect(box.decoration.runtimeType, BoxDecoration); - final BoxDecoration decoration = box.decoration as BoxDecoration; - expect(decoration.color, const ZetaPrimitivesLight().red.shade10); - await expectLater( - find.byType(ZetaInPageBanner), - matchesGoldenFile(goldenFile.getFileUri('in_page_banner_negative')), - ); - }); + final Finder decoratedBoxFinder = find.byType(DecoratedBox); + final DecoratedBox box = tester.firstWidget(decoratedBoxFinder); + expect(box.decoration.runtimeType, BoxDecoration); + final BoxDecoration decoration = box.decoration as BoxDecoration; + expect(decoration.color, const ZetaPrimitivesLight().green.shade10); + }); - testWidgets("ZetaInPageBanner hides 'close icon' correctly", (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp(home: ZetaInPageBanner(content: Text('Test'), status: ZetaWidgetStatus.positive)), - ); - expect(find.byIcon(ZetaIcons.close_round), findsNothing); - final Finder decoratedBoxFinder = find.byType(DecoratedBox); - final DecoratedBox box = tester.firstWidget(decoratedBoxFinder); - expect(box.decoration.runtimeType, BoxDecoration); - final BoxDecoration decoration = box.decoration as BoxDecoration; - expect(decoration.color, const ZetaPrimitivesLight().green.shade10); - await expectLater( - find.byType(ZetaInPageBanner), - matchesGoldenFile(goldenFile.getFileUri('in_page_banner_positive')), - ); - }); + testWidgets('neutral background colour is correct', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp(home: ZetaInPageBanner(content: Text('Test'), status: ZetaWidgetStatus.neutral)), + ); - testWidgets('ZetaInPageBanner button callbacks work', (WidgetTester tester) async { - bool onPressed = false; - final key = GlobalKey(); - await tester.pumpWidget( - TestApp( - home: ZetaInPageBanner( - content: const Text('Test'), - status: ZetaWidgetStatus.neutral, - actions: [ - ZetaButton( - label: 'Test button', - onPressed: () => onPressed = true, - key: key, - ), - ], + final Finder decoratedBoxFinder = find.byType(DecoratedBox); + final DecoratedBox box = tester.firstWidget(decoratedBoxFinder); + expect(box.decoration.runtimeType, BoxDecoration); + final BoxDecoration decoration = box.decoration as BoxDecoration; + expect(decoration.color, const ZetaPrimitivesLight().pure.shade0); + }); + }); + group('Interaction Tests', () { + testWidgets('button callback works', (WidgetTester tester) async { + bool onPressed = false; + final key = GlobalKey(); + await tester.pumpWidget( + TestApp( + home: ZetaInPageBanner( + content: const Text('Test'), + status: ZetaWidgetStatus.neutral, + actions: [ + ZetaButton( + label: 'Test button', + onPressed: () => onPressed = true, + key: key, + ), + ], + ), ), - ), - ); + ); - final Finder decoratedBoxFinder = find.byType(DecoratedBox); - final DecoratedBox box = tester.firstWidget(decoratedBoxFinder); - expect(box.decoration.runtimeType, BoxDecoration); - final BoxDecoration decoration = box.decoration as BoxDecoration; - expect(decoration.color, const ZetaPrimitivesLight().pure.shade0); + await tester.tap(find.byKey(key)); + await tester.pumpAndSettle(); + expect(onPressed, isTrue); + }); - await tester.tap(find.byKey(key)); - await tester.pumpAndSettle(); - expect(onPressed, isTrue); + testWidgets("ZetaInPageBanner 'close' icon tap test", (WidgetTester tester) async { + bool closeIconIsTapped = false; + await tester.pumpWidget( + TestApp(home: ZetaInPageBanner(onClose: () => closeIconIsTapped = true, content: const Text('Test'))), + ); + final closeIcon = find.byIcon(ZetaIcons.close_round); + await tester.tap(closeIcon); + await tester.pump(); + expect(closeIconIsTapped, isTrue); + }); + }); - await expectLater( - find.byType(ZetaInPageBanner), - matchesGoldenFile(goldenFile.getFileUri('in_page_banner_buttons')), + group('Golden Tests', () { + goldenTest( + goldenFile, + const ZetaInPageBanner(content: Text('Test'), title: 'Title'), + 'in_page_banner_default', ); - }); - testWidgets("ZetaInPageBanner 'close' icon tap test", (WidgetTester tester) async { - bool closeIconIsTapped = false; - await tester.pumpWidget( - TestApp(home: ZetaInPageBanner(onClose: () => closeIconIsTapped = true, content: const Text('Test'))), + goldenTest( + goldenFile, + ZetaInPageBanner(content: const Text('Test'), onClose: () {}, status: ZetaWidgetStatus.negative), + 'in_page_banner_negative', + ); + goldenTest( + goldenFile, + const ZetaInPageBanner(content: Text('Test'), status: ZetaWidgetStatus.positive), + 'in_page_banner_positive', + ); + goldenTest( + goldenFile, + ZetaInPageBanner( + content: const Text('Test'), + status: ZetaWidgetStatus.neutral, + actions: [ + ZetaButton( + label: 'Test button', + onPressed: () {}, + ), + ], + ), + 'in_page_banner_buttons', ); - final closeIcon = find.byIcon(ZetaIcons.close_round); - await tester.tap(closeIcon); - await tester.pump(); - expect(closeIconIsTapped, isTrue); - }); - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaInPageBanner(content: Placeholder()).debugFillProperties(diagnostics); - - expect(diagnostics.finder('onClose'), 'null'); - expect(diagnostics.finder('status'), 'info'); - expect(diagnostics.finder('title'), 'null'); - expect(diagnostics.finder('customIcon'), 'null'); }); + group('Performance Tests', () {}); } diff --git a/test/src/components/password/golden/password_default.png b/test/src/components/password/golden/password_default.png index 193fbcdd..3347869d 100644 Binary files a/test/src/components/password/golden/password_default.png and b/test/src/components/password/golden/password_default.png differ diff --git a/test/src/components/password/golden/password_error.png b/test/src/components/password/golden/password_error.png index ad0e67f5..f9cebb0f 100644 Binary files a/test/src/components/password/golden/password_error.png and b/test/src/components/password/golden/password_error.png differ diff --git a/test/src/components/password/password_input_test.dart b/test/src/components/password/password_input_test.dart index 57dbbd28..d31f0479 100644 --- a/test/src/components/password/password_input_test.dart +++ b/test/src/components/password/password_input_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -8,87 +7,111 @@ import '../../../test_utils/tolerant_comparator.dart'; import '../../../test_utils/utils.dart'; void main() { - const goldenFile = GoldenFiles(component: 'password'); + const String parentFolder = 'password'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - testWidgets('ZetaPasswordInput initializes correctly', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaPasswordInput(), - ), + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'size': 'medium', + 'placeholder': 'null', + 'label': 'null', + 'hintText': 'null', + 'errorText': 'null', + 'semanticLabel': 'null', + 'showSemanticLabel': 'null', + 'obscureSemanticLabel': 'null', + }; + debugFillPropertiesTest( + ZetaPasswordInput(), + debugFillProperties, ); - expect(find.byType(ZetaPasswordInput), findsOneWidget); - await expectLater( - find.byType(ZetaPasswordInput), - matchesGoldenFile(goldenFile.getFileUri('password_default')), - ); - }); + testWidgets('ZetaPasswordInput initializes correctly', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaPasswordInput(), + ), + ); + expect(find.byType(ZetaPasswordInput), findsOneWidget); + }); - testWidgets('Test password visibility', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaPasswordInput(), - ), - ); - final obscureIconOff = find.byIcon(ZetaIcons.visibility_off_round); - expect(obscureIconOff, findsOneWidget); - await tester.tap(obscureIconOff); - await tester.pump(); + testWidgets('obscure icon in rendered correctly', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaPasswordInput(), + ), + ); + final obscureIconOff = find.byIcon(ZetaIcons.visibility_off_round); + expect(obscureIconOff, findsOneWidget); + await tester.tap(obscureIconOff); + await tester.pump(); - final obscureIconOn = find.byIcon(ZetaIcons.visibility_round); - expect(obscureIconOn, findsOneWidget); - }); + final obscureIconOn = find.byIcon(ZetaIcons.visibility_round); + expect(obscureIconOn, findsOneWidget); + }); - testWidgets('Test error message visibility', (WidgetTester tester) async { - String? testValidator(String? value) { - final regExp = RegExp(r'\d'); - if (value != null && regExp.hasMatch(value)) return 'Error'; - return null; - } + testWidgets('Test error message visibility', (WidgetTester tester) async { + String? testValidator(String? value) { + final regExp = RegExp(r'\d'); + if (value != null && regExp.hasMatch(value)) return 'Error'; + return null; + } - final controller = TextEditingController()..text = 'password123'; - final formKey = GlobalKey(); + final controller = TextEditingController()..text = 'password123'; + final formKey = GlobalKey(); - await tester.pumpWidget( - TestApp( - home: Form( - key: formKey, - child: ZetaPasswordInput( - controller: controller, - validator: testValidator, - rounded: false, + await tester.pumpWidget( + TestApp( + home: Form( + key: formKey, + child: ZetaPasswordInput( + controller: controller, + validator: testValidator, + rounded: false, + ), ), ), - ), - ); - formKey.currentState?.validate(); - await tester.pump(); - - // There will be two matches for the error text as the form field itself contains a hidden one. - expect(find.text('Error'), findsExactly(2)); - final obscureIconOn = find.byIcon(ZetaIcons.visibility_off_sharp); - expect(obscureIconOn, findsOneWidget); + ); + formKey.currentState?.validate(); + await tester.pump(); - await expectLater( - find.byType(ZetaPasswordInput), - matchesGoldenFile(goldenFile.getFileUri('password_error')), - ); + // There will be two matches for the error text as the form field itself contains a hidden one. + expect(find.text('Error'), findsExactly(2)); + final obscureIconOn = find.byIcon(ZetaIcons.visibility_off_sharp); + expect(obscureIconOn, findsOneWidget); + }); }); - testWidgets('Test debugFillProperties', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - ZetaPasswordInput().debugFillProperties(diagnostics); - - expect(diagnostics.finder('size'), 'medium'); - expect(diagnostics.finder('placeholder'), 'null'); - expect(diagnostics.finder('label'), 'null'); - expect(diagnostics.finder('hintText'), 'null'); - expect(diagnostics.finder('errorText'), 'null'); - expect(diagnostics.finder('semanticLabel'), 'null'); - expect(diagnostics.finder('showSemanticLabel'), 'null'); - expect(diagnostics.finder('obscureSemanticLabel'), 'null'); + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () {}); + group('Golden Tests', () { + goldenTest(goldenFile, ZetaPasswordInput(), 'password_default'); + final formKey = GlobalKey(); + goldenTest( + goldenFile, + Form( + key: formKey, + child: ZetaPasswordInput( + controller: TextEditingController()..text = 'password123', + validator: (String? value) { + final regExp = RegExp(r'\d'); + if (value != null && regExp.hasMatch(value)) return 'Error'; + return null; + }, + rounded: false, + ), + ), + 'password_error', + beforeComparison: (tester) async { + formKey.currentState?.validate(); + await tester.pump(); + }, + ); }); + group('Performance Tests', () {}); } diff --git a/test/src/components/search_bar/golden/search_bar_default.png b/test/src/components/search_bar/golden/search_bar_default.png index 325a3d8e..6a27a967 100644 Binary files a/test/src/components/search_bar/golden/search_bar_default.png and b/test/src/components/search_bar/golden/search_bar_default.png differ diff --git a/test/src/components/search_bar/golden/search_bar_full.png b/test/src/components/search_bar/golden/search_bar_full.png index ac07a1ab..79189536 100644 Binary files a/test/src/components/search_bar/golden/search_bar_full.png and b/test/src/components/search_bar/golden/search_bar_full.png differ diff --git a/test/src/components/search_bar/golden/search_bar_medium.png b/test/src/components/search_bar/golden/search_bar_medium.png index 325a3d8e..6a27a967 100644 Binary files a/test/src/components/search_bar/golden/search_bar_medium.png and b/test/src/components/search_bar/golden/search_bar_medium.png differ diff --git a/test/src/components/search_bar/golden/search_bar_sharp.png b/test/src/components/search_bar/golden/search_bar_sharp.png index 033f9776..77e765e1 100644 Binary files a/test/src/components/search_bar/golden/search_bar_sharp.png and b/test/src/components/search_bar/golden/search_bar_sharp.png differ diff --git a/test/src/components/search_bar/golden/search_bar_small.png b/test/src/components/search_bar/golden/search_bar_small.png index c5eec617..68d4eff8 100644 Binary files a/test/src/components/search_bar/golden/search_bar_small.png and b/test/src/components/search_bar/golden/search_bar_small.png differ diff --git a/test/src/components/search_bar/search_bar_test.dart b/test/src/components/search_bar/search_bar_test.dart index 120b326a..6869471a 100644 --- a/test/src/components/search_bar/search_bar_test.dart +++ b/test/src/components/search_bar/search_bar_test.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -26,8 +25,9 @@ abstract class ISearchBarEvents { ]) void main() { late MockISearchBarEvents callbacks; - const goldenFile = GoldenFiles(component: 'search_bar'); + const String parentFolder = 'search_bar'; + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); @@ -36,8 +36,25 @@ void main() { callbacks = MockISearchBarEvents(); }); - group('ZetaSearchBar', () { - testWidgets('renders with default parameters', (WidgetTester tester) async { + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'size': 'medium', + 'shape': 'rounded', + 'placeholder': 'null', + 'textInputAction': 'null', + 'onSpeechToText': 'null', + 'showSpeechToText': 'true', + 'focusNode': 'null', + 'microphoneSemanticLabel': 'null', + 'clearSemanticLabel': 'null', + }; + debugFillPropertiesTest( + ZetaSearchBar(), + debugFillProperties, + ); + + testWidgets('renders text field', (WidgetTester tester) async { await tester.pumpWidget( TestApp( home: Scaffold( @@ -50,82 +67,6 @@ void main() { expect(find.byType(TextField), findsOneWidget); }); - testWidgets('golden: renders initializes correctly', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaSearchBar(), - ), - ); - expect(find.byType(ZetaSearchBar), findsOneWidget); - - await expectLater( - find.byType(ZetaSearchBar), - matchesGoldenFile(goldenFile.getFileUri('search_bar_default')), - ); - }); - - testWidgets('golden: renders size medium correctly', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaSearchBar(), - ), - ); - expect(find.byType(ZetaSearchBar), findsOneWidget); - - await expectLater( - find.byType(ZetaSearchBar), - matchesGoldenFile(goldenFile.getFileUri('search_bar_medium')), - ); - }); - - testWidgets('golden: renders size small correctly', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaSearchBar( - size: ZetaWidgetSize.small, - ), - ), - ); - expect(find.byType(ZetaSearchBar), findsOneWidget); - - await expectLater( - find.byType(ZetaSearchBar), - matchesGoldenFile(goldenFile.getFileUri('search_bar_small')), - ); - }); - - testWidgets('golden: renders shape full correctly', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaSearchBar( - shape: ZetaWidgetBorder.full, - ), - ), - ); - expect(find.byType(ZetaSearchBar), findsOneWidget); - - await expectLater( - find.byType(ZetaSearchBar), - matchesGoldenFile(goldenFile.getFileUri('search_bar_full')), - ); - }); - - testWidgets('golden: renders shape sharp correctly', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: ZetaSearchBar( - shape: ZetaWidgetBorder.sharp, - ), - ), - ); - expect(find.byType(ZetaSearchBar), findsOneWidget); - - await expectLater( - find.byType(ZetaSearchBar), - matchesGoldenFile(goldenFile.getFileUri('search_bar_sharp')), - ); - }); - testWidgets('sets initial value correctly', (WidgetTester tester) async { const initialValue = 'Initial value'; @@ -168,6 +109,25 @@ void main() { expect(find.text(updatedValue), findsOneWidget); }); + testWidgets('speech-to-text button visibility', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: Scaffold( + body: ZetaSearchBar( + showSpeechToText: false, + ), + ), + ), + ); + + await tester.pumpAndSettle(); + expect(find.byIcon(ZetaIcons.search), findsNothing); + expect(find.byIcon(ZetaIcons.microphone), findsNothing); + }); + }); + group('Dimensions Tests', () {}); + group('Styling Tests', () {}); + group('Interaction Tests', () { testWidgets('triggers onChanged callback when text is entered', (WidgetTester tester) async { await tester.pumpWidget( TestApp( @@ -237,22 +197,6 @@ void main() { expect(find.text('Disabled input'), findsNothing); }); - testWidgets('speech-to-text button visibility', (WidgetTester tester) async { - await tester.pumpWidget( - TestApp( - home: Scaffold( - body: ZetaSearchBar( - showSpeechToText: false, - ), - ), - ), - ); - - await tester.pumpAndSettle(); - expect(find.byIcon(ZetaIcons.search), findsNothing); - expect(find.byIcon(ZetaIcons.microphone), findsNothing); - }); - testWidgets('clear button functionality', (WidgetTester tester) async { await tester.pumpWidget( TestApp( @@ -273,20 +217,31 @@ void main() { verify(callbacks.onChange.call('')).called(1); }); - - test('debugFillProperties', () { - final diagnostics = DiagnosticPropertiesBuilder(); - ZetaSearchBar().debugFillProperties(diagnostics); - - expect(diagnostics.finder('size'), 'medium'); - expect(diagnostics.finder('shape'), 'rounded'); - expect(diagnostics.finder('placeholder'), 'null'); - expect(diagnostics.finder('textInputAction'), 'null'); - expect(diagnostics.finder('onSpeechToText'), 'null'); - expect(diagnostics.finder('showSpeechToText'), 'true'); - expect(diagnostics.finder('focusNode'), 'null'); - expect(diagnostics.finder('microphoneSemanticLabel'), 'null'); - expect(diagnostics.finder('clearSemanticLabel'), 'null'); - }); }); + group('Golden Tests', () { + goldenTest(goldenFile, ZetaSearchBar(), 'search_bar_default'); + goldenTest(goldenFile, ZetaSearchBar(), 'search_bar_medium'); + goldenTest( + goldenFile, + ZetaSearchBar( + size: ZetaWidgetSize.small, + ), + 'search_bar_small', + ); + goldenTest( + goldenFile, + ZetaSearchBar( + shape: ZetaWidgetBorder.full, + ), + 'search_bar_full', + ); + goldenTest( + goldenFile, + ZetaSearchBar( + shape: ZetaWidgetBorder.sharp, + ), + 'search_bar_sharp', + ); + }); + group('Performance Tests', () {}); } diff --git a/test/src/components/slider/slider_test.dart b/test/src/components/slider/slider_test.dart index 70522376..5c11e203 100644 --- a/test/src/components/slider/slider_test.dart +++ b/test/src/components/slider/slider_test.dart @@ -3,33 +3,66 @@ 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'; void main() { - testWidgets('ZetaSlider min/max values', (WidgetTester tester) async { - const double sliderValue = 0.5; - double? changedValue; - - await tester.pumpWidget( - TestApp( - home: ZetaSlider( - value: sliderValue, - onChange: (value) { - changedValue = value; - }, + const String parentFolder = 'slider'; + + const goldenFile = GoldenFiles(component: parentFolder); + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('Accessibility Tests', () {}); + + group('Content Tests', () { + // final debugFillProperties = { + // '': '', + // }; + // debugFillPropertiesTest( + // widget, + // debugFillProperties, + // ); + }); + + group('Dimensions Tests', () {}); + + group('Styling Tests', () {}); + + group('Interaction Tests', () { + testWidgets('ZetaSlider min/max values', (WidgetTester tester) async { + const double sliderValue = 0.5; + double? changedValue; + + await tester.pumpWidget( + TestApp( + home: ZetaSlider( + value: sliderValue, + onChange: (value) { + changedValue = value; + }, + ), ), - ), - ); + ); - final slider = tester.widget(find.byType(Slider)); - expect(slider.min, 0.0); - expect(slider.max, 1.0); + final slider = tester.widget(find.byType(Slider)); + expect(slider.min, 0.0); + expect(slider.max, 1.0); - // Drag the slider to the minimum value - await tester.drag(find.byType(Slider), const Offset(-400, 0)); - expect(changedValue, 0.0); + // Drag the slider to the minimum value + await tester.drag(find.byType(Slider), const Offset(-400, 0)); + expect(changedValue, 0.0); - // Drag the slider to the maximum value - await tester.drag(find.byType(Slider), const Offset(400, 0)); - expect(changedValue, 1.0); + // Drag the slider to the maximum value + await tester.drag(find.byType(Slider), const Offset(400, 0)); + expect(changedValue, 1.0); + }); }); + + group('Golden Tests', () { + // goldenTest(goldenFile, widget, widgetType, 'PNG_FILE_NAME'); + }); + + group('Performance Tests', () {}); } diff --git a/test/src/components/stepper input/stepper_input_test.dart b/test/src/components/stepper input/stepper_input_test.dart deleted file mode 100644 index 53c4b4c1..00000000 --- a/test/src/components/stepper input/stepper_input_test.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:zeta_flutter/src/components/stepper_input/stepper_input.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; - -import '../../../test_utils/test_app.dart'; - -void main() { - testWidgets('ZetaStepperInput increases value when increment button is pressed', (WidgetTester tester) async { - int value = 0; - await tester.pumpWidget( - TestApp( - home: ZetaStepperInput( - value: value, - onChange: (newValue) { - value = newValue; - }, - ), - ), - ); - - expect(value, 0); - - await tester.tap(find.byIcon(ZetaIcons.add_round)); - await tester.pump(); - - expect(value, 1); - }); - - testWidgets('ZetaStepperInput increases value when programatically changed', (WidgetTester tester) async { - int value = 0; - StateSetter? setState; - await tester.pumpWidget( - StatefulBuilder( - builder: (context, setState2) { - setState = setState2; - - return TestApp( - home: ZetaStepperInput( - value: value, - onChange: (newValue) { - value = newValue; - }, - ), - ); - }, - ), - ); - - final ZetaStepperInputState stepperInputState = tester.state(find.byType(ZetaStepperInput)); - - expect(value, stepperInputState.value); - - setState?.call(() { - value = 1; - }); - - await tester.pump(); - - expect(value, stepperInputState.value); - }); -} diff --git a/test/src/components/stepper/golden/stepper_horizontal_complete.png b/test/src/components/stepper/golden/stepper_horizontal_complete.png new file mode 100644 index 00000000..12eba6f9 Binary files /dev/null and b/test/src/components/stepper/golden/stepper_horizontal_complete.png differ diff --git a/test/src/components/stepper/golden/stepper_horizontal_incomplete.png b/test/src/components/stepper/golden/stepper_horizontal_incomplete.png new file mode 100644 index 00000000..4c552527 Binary files /dev/null and b/test/src/components/stepper/golden/stepper_horizontal_incomplete.png differ diff --git a/test/src/components/fabs/golden/FAB_disabled.png b/test/src/components/stepper/golden/stepper_horizontal_step_disabled.png similarity index 51% rename from test/src/components/fabs/golden/FAB_disabled.png rename to test/src/components/stepper/golden/stepper_horizontal_step_disabled.png index 24e1062f..22ccefba 100644 Binary files a/test/src/components/fabs/golden/FAB_disabled.png and b/test/src/components/stepper/golden/stepper_horizontal_step_disabled.png differ diff --git a/test/src/components/stepper/golden/stepper_vertical_complete.png b/test/src/components/stepper/golden/stepper_vertical_complete.png new file mode 100644 index 00000000..e8bcb704 Binary files /dev/null and b/test/src/components/stepper/golden/stepper_vertical_complete.png differ diff --git a/test/src/components/stepper/golden/stepper_vertical_incomplete.png b/test/src/components/stepper/golden/stepper_vertical_incomplete.png new file mode 100644 index 00000000..ce1bcbc7 Binary files /dev/null and b/test/src/components/stepper/golden/stepper_vertical_incomplete.png differ diff --git a/test/src/components/stepper/golden/stepper_vertical_step_disabled.png b/test/src/components/stepper/golden/stepper_vertical_step_disabled.png new file mode 100644 index 00000000..b9b01c32 Binary files /dev/null and b/test/src/components/stepper/golden/stepper_vertical_step_disabled.png differ diff --git a/test/src/components/stepper/stepper_test.dart b/test/src/components/stepper/stepper_test.dart new file mode 100644 index 00000000..b60c9399 --- /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 = 'stepper'; + + 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.mainPrimary, + ); + }, + ); + 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.mainDisabled, + ); + }, + ); + 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', () {}); +} diff --git a/test/src/components/stepper_input/stepper_input_test.dart b/test/src/components/stepper_input/stepper_input_test.dart new file mode 100644 index 00000000..95b97b06 --- /dev/null +++ b/test/src/components/stepper_input/stepper_input_test.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/src/components/stepper_input/stepper_input.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 = 'stepper_input'; + + const goldenFile = GoldenFiles(component: parentFolder); + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('Accessibility Tests', () {}); + group('Content Tests', () { + // final debugFillProperties = { + // '': '', + // }; + // debugFillPropertiesTest( + // widget, + // debugFillProperties, + // ); + + testWidgets('ZetaStepperInput increases value when programatically changed', (WidgetTester tester) async { + int value = 0; + StateSetter? setState; + await tester.pumpWidget( + StatefulBuilder( + builder: (context, setState2) { + setState = setState2; + + return TestApp( + home: ZetaStepperInput( + value: value, + onChange: (newValue) { + value = newValue; + }, + ), + ); + }, + ), + ); + + final ZetaStepperInputState stepperInputState = tester.state(find.byType(ZetaStepperInput)); + + expect(value, stepperInputState.value); + + setState?.call(() { + value = 1; + }); + + await tester.pump(); + + expect(value, stepperInputState.value); + }); + }); + + group('Dimensions Tests', () {}); + + group('Styling Tests', () {}); + + group('Interaction Tests', () { + testWidgets('ZetaStepperInput increases value when increment button is pressed', (WidgetTester tester) async { + int value = 0; + await tester.pumpWidget( + TestApp( + home: ZetaStepperInput( + value: value, + onChange: (newValue) { + value = newValue; + }, + ), + ), + ); + + expect(value, 0); + + await tester.tap(find.byIcon(ZetaIcons.add_round)); + await tester.pump(); + + expect(value, 1); + }); + }); + + group('Golden Tests', () { + // goldenTest(goldenFile, widget, widgetType, 'PNG_FILE_NAME'); + }); + + group('Performance Tests', () {}); +} diff --git a/test/src/components/tooltip/golden/arrow_down.png b/test/src/components/tooltip/golden/arrow_down.png index cfff515b..a32797db 100644 Binary files a/test/src/components/tooltip/golden/arrow_down.png and b/test/src/components/tooltip/golden/arrow_down.png differ diff --git a/test/src/components/tooltip/golden/arrow_left.png b/test/src/components/tooltip/golden/arrow_left.png index 589ff338..666475bd 100644 Binary files a/test/src/components/tooltip/golden/arrow_left.png and b/test/src/components/tooltip/golden/arrow_left.png differ diff --git a/test/src/components/tooltip/golden/arrow_right.png b/test/src/components/tooltip/golden/arrow_right.png index 3a9617c3..c9ccc4d5 100644 Binary files a/test/src/components/tooltip/golden/arrow_right.png and b/test/src/components/tooltip/golden/arrow_right.png differ diff --git a/test/src/components/tooltip/golden/arrow_up.png b/test/src/components/tooltip/golden/arrow_up.png index 0e0b0341..3abbdeb7 100644 Binary files a/test/src/components/tooltip/golden/arrow_up.png and b/test/src/components/tooltip/golden/arrow_up.png differ diff --git a/test/src/components/tooltip/tooltip_test.dart b/test/src/components/tooltip/tooltip_test.dart index 80232e3e..ed87e672 100644 --- a/test/src/components/tooltip/tooltip_test.dart +++ b/test/src/components/tooltip/tooltip_test.dart @@ -15,15 +15,55 @@ import 'tooltip_test.mocks.dart'; MockSpec(), ]) void main() { + const String parentFolder = 'tooltip'; + final mockZeta = MockZeta(); when(mockZeta.radius).thenReturn(const ZetaSemanticRadiiAA(primitives: ZetaPrimitivesLight())); - const goldenFile = GoldenFiles(component: 'tooltip'); + const goldenFile = GoldenFiles(component: parentFolder); setUpAll(() { goldenFileComparator = TolerantComparator(goldenFile.uri); }); - group('ZetaTooltip Widget Tests', () { + group('Accessibility Tests', () {}); + group('Content Tests', () { + final debugFillProperties = { + 'rounded': 'null', + 'padding': 'EdgeInsets.all(8.0)', + 'color': 'MaterialColor(primary value: Color(0xffffc107))', + 'textStyle': 'TextStyle(inherit: true, size: 9.0)', + 'arrowDirection': 'down', + 'maxWidth': '170.0', + }; + debugFillPropertiesTest( + const ZetaTooltip( + padding: EdgeInsets.all(8), + color: Colors.amber, + textStyle: TextStyle(fontSize: 9), + maxWidth: 170, + child: Text('Rounded tooltip'), + ), + debugFillProperties, + ); + + testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { + final diagnostics = DiagnosticPropertiesBuilder(); + const ZetaTooltip( + padding: EdgeInsets.all(8), + color: Colors.amber, + textStyle: TextStyle(fontSize: 9), + maxWidth: 170, + child: Text('Rounded tooltip'), + ).debugFillProperties(diagnostics); + + expect(diagnostics.finder('rounded'), 'null'); + expect(diagnostics.finder('padding'), 'EdgeInsets.all(8.0)'); + expect(diagnostics.finder('color').toLowerCase(), contains(Colors.amber.hexCode.toLowerCase())); + expect(diagnostics.finder('textStyle'), contains('size: 9.0')); + expect(diagnostics.finder('arrowDirection'), 'down'); + expect(diagnostics.finder('maxWidth'), '170.0'); + }); + testWidgets('renders with default properties', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( @@ -38,13 +78,13 @@ void main() { expect(find.text('Tooltip text'), findsOneWidget); expect(find.byType(ZetaTooltip), findsOneWidget); }); - - testWidgets('renders with custom color and padding', (WidgetTester tester) async { + }); + group('Dimensions Tests', () { + testWidgets('renders with custom padding', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( home: Scaffold( body: ZetaTooltip( - color: Colors.red, padding: EdgeInsets.all(20), child: Text('Tooltip text'), ), @@ -52,23 +92,38 @@ void main() { ), ); - final tooltipBox = tester.widget( + final padding = tester.widget( find.descendant( of: find.byType(ZetaTooltip), - matching: find.byType(DecoratedBox), + matching: find.byType(Padding), ), ); - expect((tooltipBox.decoration as BoxDecoration).color, Colors.red); + expect(padding.padding, const EdgeInsets.all(20)); + }); + }); + group('Styling Tests', () { + testWidgets('renders with custom color', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: Scaffold( + body: ZetaTooltip( + color: Colors.red, + padding: EdgeInsets.all(20), + child: Text('Tooltip text'), + ), + ), + ), + ); - final padding = tester.widget( + final tooltipBox = tester.widget( find.descendant( of: find.byType(ZetaTooltip), - matching: find.byType(Padding), + matching: find.byType(DecoratedBox), ), ); - expect(padding.padding, const EdgeInsets.all(20)); + expect((tooltipBox.decoration as BoxDecoration).color, Colors.red); }); testWidgets('renders with custom text style', (WidgetTester tester) async { @@ -99,89 +154,6 @@ void main() { expect(textSpan.style?.color, Colors.blue); }); - testWidgets('renders with arrow correctly in up direction', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: Scaffold( - body: ZetaTooltip( - arrowDirection: ZetaTooltipArrowDirection.up, - child: Text('Tooltip up'), - ), - ), - ), - ); - - expect(find.text('Tooltip up'), findsOneWidget); - - // Verifying the CustomPaint with different arrow directions. - await expectLater( - find.byType(ZetaTooltip), - matchesGoldenFile(goldenFile.getFileUri('arrow_up')), - ); - }); - - testWidgets('renders with arrow correctly in down direction', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: Scaffold( - body: ZetaTooltip( - child: Text('Tooltip down'), - ), - ), - ), - ); - - expect(find.text('Tooltip down'), findsOneWidget); - - // Verifying the CustomPaint with different arrow directions. - await expectLater( - find.byType(ZetaTooltip), - matchesGoldenFile(goldenFile.getFileUri('arrow_down')), - ); - }); - - testWidgets('renders with arrow correctly in left direction', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: Scaffold( - body: ZetaTooltip( - arrowDirection: ZetaTooltipArrowDirection.left, - child: Text('Tooltip left'), - ), - ), - ), - ); - - expect(find.text('Tooltip left'), findsOneWidget); - - // Verifying the CustomPaint with different arrow directions. - await expectLater( - find.byType(ZetaTooltip), - matchesGoldenFile(goldenFile.getFileUri('arrow_left')), - ); - }); - - testWidgets('renders with arrow correctly in right direction', (WidgetTester tester) async { - await tester.pumpWidget( - const TestApp( - home: Scaffold( - body: ZetaTooltip( - arrowDirection: ZetaTooltipArrowDirection.right, - child: Text('Tooltip right'), - ), - ), - ), - ); - - expect(find.text('Tooltip right'), findsOneWidget); - - // Verifying the CustomPaint with different arrow directions. - await expectLater( - find.byType(ZetaTooltip), - matchesGoldenFile(goldenFile.getFileUri('arrow_right')), - ); - }); - testWidgets('renders with rounded and sharp corners', (WidgetTester tester) async { await tester.pumpWidget( const TestApp( @@ -221,23 +193,52 @@ void main() { expect((roundedTooltipBox.decoration as BoxDecoration).borderRadius, mockZeta.radius.minimal); expect((sharpTooltipBox.decoration as BoxDecoration).borderRadius, null); }); - - testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { - final diagnostics = DiagnosticPropertiesBuilder(); - const ZetaTooltip( - padding: EdgeInsets.all(8), - color: Colors.amber, - textStyle: TextStyle(fontSize: 9), - maxWidth: 170, - child: Text('Rounded tooltip'), - ).debugFillProperties(diagnostics); - - expect(diagnostics.finder('rounded'), 'null'); - expect(diagnostics.finder('padding'), 'EdgeInsets.all(8.0)'); - expect(diagnostics.finder('color').toLowerCase(), contains(Colors.amber.hexCode.toLowerCase())); - expect(diagnostics.finder('textStyle'), contains('size: 9.0')); - expect(diagnostics.finder('arrowDirection'), 'down'); - expect(diagnostics.finder('maxWidth'), '170.0'); - }); }); + group('Interaction Tests', () {}); + group('Golden Tests', () { + goldenTest( + goldenFile, + const Scaffold( + body: ZetaTooltip( + arrowDirection: ZetaTooltipArrowDirection.up, + child: Text('Tooltip up'), + ), + ), + 'arrow_up', + widgetType: ZetaTooltip, + ); + goldenTest( + goldenFile, + const Scaffold( + body: ZetaTooltip( + child: Text('Tooltip down'), + ), + ), + 'arrow_down', + widgetType: ZetaTooltip, + ); + goldenTest( + goldenFile, + const Scaffold( + body: ZetaTooltip( + arrowDirection: ZetaTooltipArrowDirection.left, + child: Text('Tooltip left'), + ), + ), + 'arrow_left', + widgetType: ZetaTooltip, + ); + goldenTest( + goldenFile, + const Scaffold( + body: ZetaTooltip( + arrowDirection: ZetaTooltipArrowDirection.right, + child: Text('Tooltip right'), + ), + ), + 'arrow_right', + widgetType: ZetaTooltip, + ); + }); + group('Performance Tests', () {}); } diff --git a/test/src/components/top_app_bar/golden/extended_app_bar_shrinks.png b/test/src/components/top_app_bar/golden/extended_app_bar_shrinks.png index b821a451..9465c133 100644 Binary files a/test/src/components/top_app_bar/golden/extended_app_bar_shrinks.png and b/test/src/components/top_app_bar/golden/extended_app_bar_shrinks.png differ diff --git a/test/src/components/top_app_bar/golden/extended_app_bar_shrinks_with_no_leading.png b/test/src/components/top_app_bar/golden/extended_app_bar_shrinks_with_no_leading.png index 7b4e3f6e..81600972 100644 Binary files a/test/src/components/top_app_bar/golden/extended_app_bar_shrinks_with_no_leading.png and b/test/src/components/top_app_bar/golden/extended_app_bar_shrinks_with_no_leading.png differ diff --git a/test/src/components/fabs/golden/FAB_secondary.png b/test/src/components/top_app_bar/golden/top_app_bar_centered.png similarity index 77% rename from test/src/components/fabs/golden/FAB_secondary.png rename to test/src/components/top_app_bar/golden/top_app_bar_centered.png index a55e9f06..e51402a1 100644 Binary files a/test/src/components/fabs/golden/FAB_secondary.png and b/test/src/components/top_app_bar/golden/top_app_bar_centered.png differ diff --git a/test/src/components/top_app_bar/golden/top_app_bar_centered_actions.png b/test/src/components/top_app_bar/golden/top_app_bar_centered_actions.png new file mode 100644 index 00000000..c4c1d4b1 Binary files /dev/null and b/test/src/components/top_app_bar/golden/top_app_bar_centered_actions.png differ diff --git a/test/src/components/fabs/golden/FAB_inverse.png b/test/src/components/top_app_bar/golden/top_app_bar_default.png similarity index 60% rename from test/src/components/fabs/golden/FAB_inverse.png rename to test/src/components/top_app_bar/golden/top_app_bar_default.png index c6d630fc..76f6c413 100644 Binary files a/test/src/components/fabs/golden/FAB_inverse.png and b/test/src/components/top_app_bar/golden/top_app_bar_default.png differ diff --git a/test/src/components/top_app_bar/golden/top_app_bar_default_actions.png b/test/src/components/top_app_bar/golden/top_app_bar_default_actions.png new file mode 100644 index 00000000..99ca687e Binary files /dev/null and b/test/src/components/top_app_bar/golden/top_app_bar_default_actions.png differ diff --git a/test/src/components/top_app_bar/golden/top_app_bar_extended.png b/test/src/components/top_app_bar/golden/top_app_bar_extended.png new file mode 100644 index 00000000..c8240481 Binary files /dev/null and b/test/src/components/top_app_bar/golden/top_app_bar_extended.png differ diff --git a/test/src/components/top_app_bar/golden/top_app_bar_extended_actions.png b/test/src/components/top_app_bar/golden/top_app_bar_extended_actions.png new file mode 100644 index 00000000..5c252bee Binary files /dev/null and b/test/src/components/top_app_bar/golden/top_app_bar_extended_actions.png differ diff --git a/test/src/components/top_app_bar/golden/top_app_bar_search.png b/test/src/components/top_app_bar/golden/top_app_bar_search.png new file mode 100644 index 00000000..758acd24 Binary files /dev/null and b/test/src/components/top_app_bar/golden/top_app_bar_search.png differ diff --git a/test/src/components/top_app_bar/golden/top_app_bar_search_active.png b/test/src/components/top_app_bar/golden/top_app_bar_search_active.png new file mode 100644 index 00000000..bab4887c Binary files /dev/null and b/test/src/components/top_app_bar/golden/top_app_bar_search_active.png differ diff --git a/test/src/components/top_app_bar/golden/top_app_bar_search_centered.png b/test/src/components/top_app_bar/golden/top_app_bar_search_centered.png new file mode 100644 index 00000000..a724f2b6 Binary files /dev/null and b/test/src/components/top_app_bar/golden/top_app_bar_search_centered.png differ diff --git a/test/src/components/top_app_bar/top_app_bar_test.dart b/test/src/components/top_app_bar/top_app_bar_test.dart new file mode 100644 index 00000000..8ae60e83 --- /dev/null +++ b/test/src/components/top_app_bar/top_app_bar_test.dart @@ -0,0 +1,557 @@ +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'; + +void main() { + const String parentFolder = 'top_app_bar'; + + const goldenFile = GoldenFiles(component: parentFolder); + setUpAll(() { + goldenFileComparator = TolerantComparator(goldenFile.uri); + }); + + group('ZetaTopAppBar Accessibility Tests', () { + testWidgets('ZetaTopAppBar meets accessibility requirements', (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + TestApp( + home: ZetaTopAppBar( + title: const Text('Title'), + actions: [ + IconButton( + icon: const Icon(Icons.search), + tooltip: 'Search', + onPressed: () {}, + ), + ], + leading: IconButton( + icon: const Icon(Icons.menu), + tooltip: 'Menu', + onPressed: () {}, + ), + ), + ), + ); + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + handle.dispose(); + }); + + testWidgets('ZetaTopAppBar passes semantic labels to the search actions', (WidgetTester tester) async { + const microphoneSemanticLabel = 'Search with voice'; + const clearSemanticLabel = 'Clear search'; + const searchBackSemanticLabel = 'Back'; + const searchSemanticLabel = 'Search'; + final ZetaSearchController searchController = ZetaSearchController(); + await tester.pumpWidget( + TestApp( + home: ZetaTopAppBar.search( + title: const Text('Title'), + microphoneSemanticLabel: microphoneSemanticLabel, + clearSemanticLabel: clearSemanticLabel, + searchController: searchController, + searchSemanticLabel: searchSemanticLabel, + searchBackSemanticLabel: searchBackSemanticLabel, + onSearchMicrophoneIconPressed: () {}, + ), + ), + ); + + expect( + find.bySemanticsLabel(searchSemanticLabel), + findsOneWidget, + ); + + searchController.startSearch(); + await tester.pumpAndSettle(); + + expect( + find.bySemanticsLabel(microphoneSemanticLabel), + findsOneWidget, + ); + expect( + find.bySemanticsLabel(clearSemanticLabel), + findsOneWidget, + ); + expect( + find.bySemanticsLabel(searchBackSemanticLabel), + findsOneWidget, + ); + }); + }); + + group('ZetaTopAppBar Content Tests', () { + final debugFillProperties = { + 'titleTextStyle': 'null', + 'onSearch': 'null', + 'automaticallyImplyLeading': 'true', + 'onSearchMicrophoneIconPressed': 'null', + 'searchController': 'null', + 'searchHintText': 'null', + 'type': 'defaultAppBar', + 'shrinks': 'false', + 'clearSemanticLabel': 'null', + 'microphoneSemanticLabel': 'null', + 'searchSemanticLabel': 'null', + 'searchBackSemanticLabel': 'null', + }; + debugFillPropertiesTest( + const ZetaTopAppBar(), + debugFillProperties, + ); + + test( + 'ZetaTopAppBar Search throws an assertion error if its type is set to extended', + () { + expect( + () => ZetaTopAppBar.search( + title: const Text('Title'), + type: ZetaTopAppBarType.extended, + ), + throwsA(isA()), + ); + }, + ); + + testWidgets('ZetaTopAppBar displays the title correctly', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaTopAppBar( + title: Text('Title'), + ), + ), + ); + + expect(find.text('Title'), findsOneWidget); + }); + + testWidgets('ZetaTopAppBar displays the leading widget correctly', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaTopAppBar( + title: const Text('Title'), + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () {}, + ), + ), + ), + ); + + expect(find.byIcon(Icons.menu), findsOneWidget); + }); + + testWidgets('ZetaTopAppBar displays the actions correctly', (WidgetTester tester) async { + await tester.pumpWidget( + TestApp( + home: ZetaTopAppBar( + title: const Text('Title'), + actions: [ + IconButton( + icon: const Icon(Icons.search), + onPressed: () {}, + ), + ], + ), + ), + ); + + expect(find.byIcon(Icons.search), findsOneWidget); + }); + + testWidgets('ZetaExtendedAppBarDelegate builds correctly', (WidgetTester tester) async { + const title = Text('Title'); + final actions = [IconButton(icon: const Icon(Icons.search), onPressed: () {})]; + final leading = IconButton(icon: const Icon(Icons.menu), onPressed: () {}); + const boxKey = Key('box'); + + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return SizedBox( + child: CustomScrollView( + slivers: [ + ZetaTopAppBar.extended(leading: leading, title: title, actions: actions), + SliverToBoxAdapter( + child: Container( + width: 800, + height: 700, + color: Zeta.of(context).colors.surfaceSelectedHover, + key: boxKey, + ), + ), + ], + ), + ); + }, + ), + ), + ); + + final boxFinder = find.byKey(boxKey); + expect(boxFinder, findsOneWidget); + + await tester.drag(boxFinder.first, const Offset(0, -100)); + await tester.pumpAndSettle(); + + final appBarFinder = find.byType(ZetaTopAppBar); + expect(appBarFinder, findsOneWidget); + + final titleFinder = find.descendant(of: appBarFinder, matching: find.byWidget(title)); + expect(titleFinder, findsOneWidget); + + final actionsFinder = find.descendant(of: appBarFinder, matching: find.byWidget(actions[0])); + expect(actionsFinder, findsOneWidget); + + final leadingFinder = find.descendant(of: appBarFinder, matching: find.byWidget(leading)); + expect(leadingFinder, findsOneWidget); + }); + }); + + group('ZetaTopAppBar Dimensions Tests', () { + testWidgets('ZetaTopAppBar has the correct height', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaTopAppBar( + title: Text('Title'), + ), + ), + ); + + final appBar = tester.widget(find.byType(AppBar)); + expect(appBar.preferredSize.height, 64); + }); + }); + + group('ZetaTopAppBar Styling Tests', () { + testWidgets('ZetaTopAppBar has the correct background color', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: ZetaTopAppBar( + title: Text('Title'), + ), + ), + ); + + final BuildContext context = tester.element(find.byType(MaterialApp)); + final appBar = tester.widget(find.byType(AppBar)); + expect(appBar.backgroundColor, Zeta.of(context).colors.surfaceDefault); + }); + }); + + group('ZetaTopAppBar Interaction Tests', () { + late ZetaSearchController searchController; + const searchLabel = 'Search'; + const clearLabel = 'Clear'; + const backLabel = 'Back'; + + late Widget subject; + + setUp(() { + searchController = ZetaSearchController(); + subject = TestApp( + home: ZetaTopAppBar.search( + title: const Text('Title'), + searchController: searchController, + searchSemanticLabel: searchLabel, + clearSemanticLabel: clearLabel, + searchBackSemanticLabel: backLabel, + ), + ); + }); + + testWidgets( + 'ZetaTopAppBar Search opens and closes the search bar when the search/back icon is tapped', + (WidgetTester tester) async { + await tester.pumpWidget(subject); + + expect(searchController.isEnabled, isFalse); + await tester.tap(find.bySemanticsLabel(searchLabel)); + await tester.pumpAndSettle(); + + expect(searchController.isEnabled, isTrue); + + await tester.tap(find.bySemanticsLabel(backLabel)); + await tester.pumpAndSettle(); + + expect(searchController.isEnabled, isFalse); + }, + ); + + testWidgets( + 'ZetaTopAppBar Search allows text to be typed in the search field', + (WidgetTester tester) async { + await tester.pumpWidget(subject); + + searchController.startSearch(); + await tester.pumpAndSettle(); + + await tester.enterText(find.byType(TextField), 'Search text'); + expect(searchController.text, 'Search text'); + }, + ); + + testWidgets( + 'ZetaTopAppBar Search gets cleared when the clear button is tapped', + (WidgetTester tester) async { + await tester.pumpWidget(subject); + + searchController.startSearch(); + await tester.pumpAndSettle(); + + await tester.enterText(find.byType(TextField), 'Search text'); + await tester.tap(find.bySemanticsLabel(clearLabel)); + expect(searchController.text, ''); + }, + ); + + testWidgets( + 'ZetaTopAppBar Search submits the correct text when the search input is submitted', + (WidgetTester tester) async { + String inputtedText = ''; + await tester.pumpWidget( + TestApp( + home: ZetaTopAppBar.search( + title: const Text('Title'), + searchController: searchController, + onSearch: (String text) { + inputtedText = text; + }, + ), + ), + ); + + searchController.startSearch(); + await tester.pumpAndSettle(); + + await tester.enterText(find.byType(TextField), 'Search text'); + await tester.testTextInput.receiveAction(TextInputAction.done); + expect(inputtedText, 'Search text'); + }, + ); + + testWidgets('ZetaExtendedAppBarDelegate shrinks correctly with padding', (WidgetTester tester) async { + const title = Text('Title'); + final actions = [IconButton(icon: const Icon(Icons.search), onPressed: () {})]; + final leading = IconButton(icon: const Icon(Icons.menu), onPressed: () {}); + const boxKey = Key('box'); + + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return SizedBox( + child: CustomScrollView( + slivers: [ + ZetaTopAppBar.extended(leading: leading, title: title, actions: actions), + SliverToBoxAdapter( + child: Container( + width: 800, + height: 700, + color: Zeta.of(context).colors.surfaceSelectedHover, + key: boxKey, + ), + ), + ], + ), + ); + }, + ), + ), + ); + + final boxFinder = find.byKey(boxKey); + expect(boxFinder, findsOneWidget); + + await tester.drag(boxFinder.first, const Offset(0, -100)); + await tester.pumpAndSettle(); + + final appBarFinder = find.byType(ZetaTopAppBar); + expect(appBarFinder, findsOneWidget); + + final positionedFinder = find.descendant(of: appBarFinder, matching: find.byType(Positioned)); + + final positionedWidget = tester.widget(positionedFinder.first); + expect(positionedWidget.left, 64); + }); + + testWidgets('ZetaExtendedAppBarDelegate shrinks correctly with padding and no leading', + (WidgetTester tester) async { + const title = Text('Title'); + final actions = [IconButton(icon: const Icon(Icons.search), onPressed: () {})]; + + const boxKey = Key('box'); + + await tester.pumpWidget( + TestApp( + home: Builder( + builder: (context) { + return SizedBox( + child: CustomScrollView( + slivers: [ + ZetaTopAppBar.extended(title: title, actions: actions), + SliverToBoxAdapter( + child: Container( + width: 800, + height: 700, + color: Zeta.of(context).colors.surfaceSelectedHover, + key: boxKey, + ), + ), + ], + ), + ); + }, + ), + ), + ); + + final boxFinder = find.byKey(boxKey); + expect(boxFinder, findsOneWidget); + + await tester.drag(boxFinder.first, const Offset(0, -100)); + await tester.pumpAndSettle(); + + final appBarFinder = find.byType(ZetaTopAppBar); + expect(appBarFinder, findsOneWidget); + + final positionedFinder = find.descendant(of: appBarFinder, matching: find.byType(Positioned)); + + final positionedWidget = tester.widget(positionedFinder.first); + expect(positionedWidget.left, 16); + }); + }); + + group('ZetaTopAppBar Golden Tests', () { + goldenTest( + goldenFile, + const ZetaTopAppBar( + title: Text('Title'), + ), + 'top_app_bar_default', + ); + + goldenTest( + goldenFile, + ZetaTopAppBar( + title: const Text('Title'), + leading: IconButton(onPressed: () {}, icon: const Icon(Icons.menu)), + actions: [ + IconButton( + icon: const Icon(Icons.search), + tooltip: 'Search', + onPressed: () {}, + ), + ], + ), + 'top_app_bar_default_actions', + ); + + goldenTest( + goldenFile, + const ZetaTopAppBar.centered( + title: Text('Title'), + ), + 'top_app_bar_centered', + ); + + goldenTest( + goldenFile, + ZetaTopAppBar.centered( + leading: IconButton(onPressed: () {}, icon: const Icon(Icons.menu)), + actions: [ + IconButton( + icon: const Icon(Icons.search), + tooltip: 'Search', + onPressed: () {}, + ), + ], + title: const Text('Title'), + ), + 'top_app_bar_centered_actions', + ); + + goldenTest( + goldenFile, + const ZetaTopAppBar.search( + title: Text('Search'), + ), + 'top_app_bar_search', + ); + + goldenTest( + goldenFile, + const ZetaTopAppBar.search( + title: Text('Search'), + type: ZetaTopAppBarType.centered, + ), + 'top_app_bar_search_centered', + ); + + final searchController = ZetaSearchController(); + goldenTest( + goldenFile, + ZetaTopAppBar.search( + title: const Text('Search'), + type: ZetaTopAppBarType.centered, + searchController: searchController, + ), + beforeComparison: (tester) async { + searchController.startSearch(); + await tester.pumpAndSettle(); + }, + 'top_app_bar_search_active', + ); + + goldenTest( + goldenFile, + const CustomScrollView( + slivers: [ + ZetaTopAppBar.extended( + title: Text('Title'), + ), + ], + ), + widgetType: ZetaTopAppBar, + beforeComparison: (tester) async { + searchController.startSearch(); + await tester.pumpAndSettle(); + }, + 'top_app_bar_extended', + ); + + goldenTest( + goldenFile, + CustomScrollView( + slivers: [ + ZetaTopAppBar.extended( + title: const Text('Title'), + actions: [ + IconButton( + icon: const Icon(Icons.search), + tooltip: 'Search', + onPressed: () {}, + ), + ], + ), + ], + ), + widgetType: ZetaTopAppBar, + beforeComparison: (tester) async { + searchController.startSearch(); + await tester.pumpAndSettle(); + }, + 'top_app_bar_extended_actions', + ); + }); + + group('ZetaTopAppBar Performance Tests', () {}); +} diff --git a/test/test_utils/utils.dart b/test/test_utils/utils.dart index 1bfb59f1..ec5c9704 100644 --- a/test/test_utils/utils.dart +++ b/test/test_utils/utils.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart'; +import '../test_utils/test_app.dart'; extension Util on DiagnosticPropertiesBuilder { dynamic finder(String finder) { @@ -30,6 +31,53 @@ class GoldenFiles { } } +void goldenTest( + GoldenFiles goldenFile, + Widget widget, + String fileName, { + Type? widgetType, + ThemeMode themeMode = ThemeMode.system, + Size? screenSize, + bool? rounded, + Future Function(WidgetTester)? setUp, + Future Function(WidgetTester)? beforeComparison, +}) { + testWidgets('$fileName golden', (WidgetTester tester) async { + final computedType = widgetType ?? widget.runtimeType; + if (setUp != null) { + await setUp(tester); + } + await tester.pumpWidget( + TestApp( + screenSize: screenSize, + themeMode: themeMode, + rounded: rounded, + home: widget, + ), + ); + + if (beforeComparison != null) { + await beforeComparison(tester); + } + + await expectLater( + find.byType(computedType), + matchesGoldenFile(goldenFile.getFileUri(fileName)), + ); + }); +} + BuildContext getBuildContext(WidgetTester tester, Type type) { return tester.element(find.byType(type)); } + +void debugFillPropertiesTest(Widget widget, Map properties) { + testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { + final diagnostics = DiagnosticPropertiesBuilder(); + widget.debugFillProperties(diagnostics); + + properties.forEach((key, value) { + expect(diagnostics.finder(key), value); + }); + }); +}