diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b53985d..24c127c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,6 +34,127 @@ For more information on widgetbook, [read the docs](https://docs.widgetbook.io/) We should also create a test for each widget created. +### Contributing Guide for Writing Tests + +#### Folder Structure + +To maintain consistency and ease of navigation, follow the same folder structure in the `test` directory as in the `lib` directory. For example, if you have a file located at `lib/src/components/tooltip/tooltip.dart`, your test file should be located at `test/src/components/tooltip/tooltip_test.dart`. + +##### Example Folder Structure + +``` +lib/ +└── src/ + └── components/ + └── tooltip/ + └── tooltip.dart + +test/ +└── src/ + └── components/ + └── tooltip/ + └── tooltip_test.dart +``` + +#### Writing Tests + +1. **Unit Tests**: Test individual functions and classes. +2. **Widget Tests**: Test individual widgets and their interactions. +3. **Integration Tests**: Test the complete app or large parts of it. + +##### Guidelines + +- Use descriptive test names. +- Test one thing per test. +- Mock dependencies using `mockito`. +- Write tests for edge cases. + +#### Measuring Code Coverage + +To ensure high code coverage (at least > 96%), use the following steps: + +##### With 'lcov' +1. **Run the script coverage.sh from the project root, which make use of `lcov` to generate the coverage report**: + ```sh + sh coverage.sh + ``` +##### Alternatively, with 'genhtml' +1. **Install dependencies**: + ```sh + flutter pub add --dev test coverage + ``` + +2. **Run tests with coverage**: + ```sh + flutter test --coverage + ``` + +3. **Generate coverage report**: + ```sh + genhtml coverage/lcov.info -o coverage/html + ``` + +4. **View coverage report**: + Open `coverage/html/index.html` in a web browser. + + +##### Maximizing Coverage + +- Write tests for all public methods and classes. +- Include tests for edge cases and error handling. +- Avoid excluding files from coverage unless necessary. + +#### Golden Tests + +Golden tests are used to ensure that the visual output of your widgets remains consistent over time. They are particularly useful for complex UI components. + +##### Why Golden Tests? + +In the `tooltip` example, the direction of the arrows in the tooltip is crucial. Golden tests help to ensure that any changes to the tooltip do not unintentionally alter the direction of the arrows or other visual aspects. + +##### Adding Golden Tests + +1. **Set up golden tests**: + ```dart + import 'package:flutter_test/flutter_test.dart'; + import 'package:zeta_flutter/zeta_flutter.dart'; + + import '../../../test_utils/test_app.dart'; + + void main() { + 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(p.join('golden', 'arrow_up.png')), + ); + }); + } + ``` + +2. **Run golden tests**: + ```sh + flutter test --update-goldens + ``` + +3. **Verify golden tests**: + Ensure that the generated golden files are correct and commit them to the repository. + + + ## Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests (PRs) for this purpose. Consult [GitHub Help](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) for more information on using pull requests. diff --git a/pubspec.yaml b/pubspec.yaml index 4187fe6d..4b15abc6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.4.4 + path: ^1.9.0 zds_analysis: ^1.0.0 flutter: diff --git a/test/src/components/tooltip/golden/arrow_down.png b/test/src/components/tooltip/golden/arrow_down.png new file mode 100644 index 00000000..aa995748 Binary files /dev/null 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 new file mode 100644 index 00000000..7f66b33e Binary files /dev/null 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 new file mode 100644 index 00000000..30d99000 Binary files /dev/null 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 new file mode 100644 index 00000000..da590cb5 Binary files /dev/null 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 new file mode 100644 index 00000000..4113647e --- /dev/null +++ b/test/src/components/tooltip/tooltip_test.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart' as p; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../../test_utils/test_app.dart'; + +void main() { + group('ZetaTooltip Widget Tests', () { + testWidgets('renders with default properties', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: Scaffold( + body: ZetaTooltip( + child: Text('Tooltip text'), + ), + ), + ), + ); + + expect(find.text('Tooltip text'), findsOneWidget); + expect(find.byType(ZetaTooltip), findsOneWidget); + }); + + testWidgets('renders with custom color and padding', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: Scaffold( + body: ZetaTooltip( + color: Colors.red, + padding: EdgeInsets.all(20), + child: Text('Tooltip text'), + ), + ), + ), + ); + + final tooltipBox = tester.widget( + find.descendant( + of: find.byType(ZetaTooltip), + matching: find.byType(DecoratedBox), + ), + ); + + expect((tooltipBox.decoration as BoxDecoration).color, Colors.red); + + final padding = tester.widget( + find.descendant( + of: find.byType(ZetaTooltip), + matching: find.byType(Padding), + ), + ); + + expect(padding.padding, const EdgeInsets.all(20)); + }); + + testWidgets('renders with custom text style', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: Scaffold( + body: ZetaTooltip( + textStyle: TextStyle(fontSize: 24, color: Colors.blue), + child: Text('Tooltip text'), + ), + ), + ), + ); + + // Find the RichText widget, which is a descendant of the Text widget + final richTextFinder = find.descendant( + of: find.text('Tooltip text'), + matching: find.byType(RichText), + ); + + expect(richTextFinder, findsOneWidget); + + // Verify the styles + final RichText richTextWidget = tester.widget(richTextFinder); + final TextSpan textSpan = richTextWidget.text as TextSpan; + + expect(textSpan.style?.fontSize, 24); + 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(p.join('golden', 'arrow_up.png')), + ); + }); + + 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(p.join('golden', 'arrow_down.png')), + ); + }); + + 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(p.join('golden', 'arrow_left.png')), + ); + }); + + 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(p.join('golden', 'arrow_right.png')), + ); + }); + + testWidgets('renders with rounded and sharp corners', (WidgetTester tester) async { + await tester.pumpWidget( + const TestApp( + home: Scaffold( + body: Column( + children: [ + ZetaTooltip( + child: Text('Rounded tooltip'), + ), + ZetaTooltip( + rounded: false, + child: Text('Sharp tooltip'), + ), + ], + ), + ), + ), + ); + + expect(find.text('Rounded tooltip'), findsOneWidget); + expect(find.text('Sharp tooltip'), findsOneWidget); + + final roundedTooltipBox = tester.widget( + find.descendant( + of: find.widgetWithText(ZetaTooltip, 'Rounded tooltip'), + matching: find.byType(DecoratedBox), + ), + ); + + final sharpTooltipBox = tester.widget( + find.descendant( + of: find.widgetWithText(ZetaTooltip, 'Sharp tooltip'), + matching: find.byType(DecoratedBox), + ), + ); + + expect((roundedTooltipBox.decoration as BoxDecoration).borderRadius, ZetaRadius.minimal); + expect((sharpTooltipBox.decoration as BoxDecoration).borderRadius, null); + }); + }); +}