Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add tests and docs #1

Merged
merged 9 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@Juliotati
47 changes: 47 additions & 0 deletions .github/workflows/ci_tests_pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Tests Pipeline

on:
pull_request:
types:
- opened
- synchronize
branches:
- main

jobs:
analyze-and-test:
runs-on: macos-latest
steps:
- name: setup-repo
uses: actions/checkout@v4

- name: setup-jdk
uses: actions/setup-java@v2
with:
java-version: '12.x'
distribution: 'zulu'

- uses: subosito/flutter-action@v2
with:
cache: true
channel: 'stable'
cache-key: 'flutter-macos-stable-3.24.0-x64'
cache-path: '${{ runner.tool_cache }}/flutter-macos-stable-3.24.0-x64-hash'
pub-cache-key: 'flutter-pub-macos-stable-3.24.0-x64-hash'
pub-cache-path: '${{ runner.tool_cache }}/flutter/stable-3.24.0-x64'
flutter-version: '3.24.0'

- name: load-dependencies
run: dart pub get

- name: analyse-link_target
run: dart analyze --fatal-warnings

- name: analyse-example
run: dart analyze example --fatal-warnings

- name: validate-format
run: dart format --line-length 80 --set-exit-if-changed .

- name: run-tests
run: flutter test
7 changes: 7 additions & 0 deletions .run/RUN_WEB_AUTO.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RUN_WEB_AUTO" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="additionalArgs" value="-d chrome --web-renderer auto" />
<option name="filePath" value="$PROJECT_DIR$/example/lib/main.dart" />
<method v="2" />
</configuration>
</component>
25 changes: 5 additions & 20 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ class LinkTargetExample extends StatelessWidget {

@override
Widget build(BuildContext context) {
final titleMedium = Theme.of(context).textTheme.titleMedium;
return MaterialApp(
title: 'Link Target Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: LinkTargetRegion(
child: Scaffold(
body: Center(
Expand All @@ -26,31 +23,19 @@ class LinkTargetExample extends StatelessWidget {
children: [
LinkTargetDetector(
target: 'https://github.com/flutter/flutter',
child: Text(
'Preview flutter repo',
style: Theme.of(context).textTheme.titleMedium,
),
child: Text('Preview flutter repo', style: titleMedium),
),
LinkTargetDetector(
target: 'https://flutter.dev/development',
child: Text(
'Preview flutter.dev',
style: Theme.of(context).textTheme.titleMedium,
),
child: Text('Preview flutter.dev', style: titleMedium),
),
LinkTargetDetector(
target: 'https://dart.dev/',
child: Text(
'Preview dart.dev',
style: Theme.of(context).textTheme.titleMedium,
),
child: Text('Preview dart.dev', style: titleMedium),
),
LinkTargetDetector(
target: 'https://www.youtube.com/@flutterdev',
child: Text(
'Preview Flutter YouTube',
style: Theme.of(context).textTheme.titleMedium,
),
child: Text('Preview Flutter YouTube', style: titleMedium),
),
],
),
Expand Down
9 changes: 9 additions & 0 deletions lib/src/detector.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:link_target/src/provider.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -31,6 +34,12 @@ class _LinkTargetDetectorState extends State<LinkTargetDetector> {

@override
Widget build(BuildContext context) {
if (!kIsWeb) {
if (!Platform.environment.containsKey('FLUTTER_TEST')) {
return widget.child;
}
}

return MouseRegion(
onHover: (_) {
if (_isHovered) return;
Expand Down
4 changes: 3 additions & 1 deletion lib/src/provider.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:developer';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

/// Manages and provides link target displayed by [LinkTargetRegion].
final class LinkTargetProvider extends ChangeNotifier {
Expand All @@ -15,6 +16,7 @@ final class LinkTargetProvider extends ChangeNotifier {
/// Updates the target "URL" shown on the page by [LinkTargetRegion]
void onHover(String value) {
_linkTarget = value.trim();
if (kDebugMode) log(name: 'LinkTargetProvider', 'onHover: $_linkTarget');
notifyListeners();
}

Expand Down
10 changes: 9 additions & 1 deletion lib/src/region.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:link_target/src/provider.dart';
Expand All @@ -19,7 +21,11 @@ final class LinkTargetRegion extends StatelessWidget {

@override
Widget build(BuildContext context) {
if (!kIsWeb) return child;
if (!kIsWeb) {
if (!Platform.environment.containsKey('FLUTTER_TEST')) {
return child;
}
}

return ListenableProvider(
create: (_) => LinkTargetProvider(),
Expand Down Expand Up @@ -53,6 +59,8 @@ final class LinkTargetRegion extends StatelessWidget {
),
child: Text(
provider.linkTarget,
maxLines: 1,
softWrap: false,
style: const TextStyle(
fontSize: 12,
color: Color.fromRGBO(240, 240, 240, 1.0),
Expand Down
23 changes: 23 additions & 0 deletions test/src/detector_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter/material.dart' show MouseRegion, Text, TextDirection;
import 'package:flutter_test/flutter_test.dart';
import 'package:link_target/src/detector.dart';

void main() {
testWidgets(
'LinkTargetDetector should be build correctly',
(tester) async {
await tester.pumpWidget(
const LinkTargetDetector(
target: 'https://example.com',
child: Text(
'Click here',
textDirection: TextDirection.ltr,
),
),
);

expect(find.text('Click here'), findsOneWidget);
expect(find.byType(MouseRegion), findsOneWidget);
},
);
}
176 changes: 176 additions & 0 deletions test/src/region_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show Card, MaterialApp, Scaffold, Text;
import 'package:flutter_test/flutter_test.dart';
import 'package:link_target/link_target.dart';

void main() {
group('LinkTargetRegion', () {
Future<void> hasPositionedAndAnimatedSwitchWidget() async {
final positioned = find
.ancestor(of: find.byType(Stack), matching: find.byType(Positioned))
.evaluate()
.first
.widget as Positioned;

expect(positioned.left, 0.0);
expect(positioned.bottom, 0.0);
expect(positioned.top, isNull);
expect(positioned.right, isNull);
expect(positioned.width, isNull);
expect(positioned.height, isNull);

final switcher = find.byType(AnimatedSwitcher).evaluate().first.widget
as AnimatedSwitcher;

expect(switcher.duration, const Duration(milliseconds: 200));
expect(switcher.reverseDuration, const Duration(milliseconds: 300));
}

Future<({Rect rect, TestGesture gesture})> hoverOnText(
WidgetTester tester,
String text,
) async {
final textRect = tester.getRect(find.text(text));
final gesture = await tester.startGesture(
textRect.topCenter,
kind: PointerDeviceKind.mouse,
);

await gesture.up();
await gesture.moveTo(textRect.topLeft);
await tester.pumpAndSettle(const Duration(milliseconds: 550));

return (rect: textRect, gesture: gesture);
}

testWidgets('should layout empty sizedBox', (tester) async {
await tester.pumpWidget(const LinkTargetExample());

await hasPositionedAndAnimatedSwitchWidget();

expect(find.byType(SizedBox), findsOneWidget);
expect(find.byType(Card), findsNothing);
});

testWidgets('should layout link target content', (tester) async {
await tester.pumpWidget(const LinkTargetExample());

await hasPositionedAndAnimatedSwitchWidget();
await hoverOnText(tester, 'Preview flutter repo');

final card = find.byType(Card).evaluate().first.widget as Card;

expect(find.byType(SizedBox), findsNothing);
expect(card.elevation, 0.0);
expect(card.margin, EdgeInsets.zero);
expect(card.color, const Color.fromRGBO(40, 40, 40, 1.0));
expect(
card.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.only(topRight: Radius.circular(6.0)),
),
);
expect(
(card.child as Padding).padding,
const EdgeInsets.symmetric(vertical: 6.0, horizontal: 8.0),
);

final text = (card.child as Padding).child as Text;

expect(text.data, 'https://github.com/flutter/flutter');
expect(text.maxLines, 1);
expect(text.softWrap, isFalse);
expect(
text.style,
const TextStyle(
fontSize: 12,
color: Color.fromRGBO(240, 240, 240, 1.0),
),
);
});

testWidgets('should hover and exit single widget', (tester) async {
await tester.pumpWidget(const LinkTargetExample());

expect(find.text('https://github.com/flutter/flutter'), findsNothing);

// 1. Hovers on a single widget
final hover = await hoverOnText(tester, 'Preview flutter repo');

expect(find.text('https://github.com/flutter/flutter'), findsOneWidget);

// 2. Exits hover mode and moves away from all widgets
await hover.gesture.moveTo(const Offset(0.0, 0.0));
await tester.pumpAndSettle(const Duration(milliseconds: 300));

expect(find.text('https://github.com/flutter/flutter'), findsNothing);
});

testWidgets('should hover on multiple widgets at a time', (tester) async {
await tester.pumpWidget(const LinkTargetExample());
expect(find.text('https://github.com/flutter/flutter'), findsNothing);

// 1. Hovers on the first widget
final hover = await hoverOnText(tester, 'Preview flutter repo');

expect(find.text('https://github.com/flutter/flutter'), findsOneWidget);
expect(find.text('https://flutter.dev/development'), findsNothing);

// 2. Exits first widget and hovers on the second widget
await hover.gesture.moveTo(hover.rect.bottomLeft);
await tester.pumpAndSettle(const Duration(milliseconds: 300));

expect(find.text('https://github.com/flutter/flutter'), findsNothing);
expect(find.text('https://flutter.dev/development'), findsOneWidget);

// 3. Exits hover mode and moves away from all widgets
await hover.gesture.down(const Offset(0.0, 0.0));
await hover.gesture.cancel();
await tester.pumpAndSettle(const Duration(milliseconds: 300));

expect(find.text('https://github.com/flutter/flutter'), findsNothing);
expect(find.text('https://flutter.dev/development'), findsNothing);
});
});
}

class LinkTargetExample extends StatelessWidget {
const LinkTargetExample({super.key});

@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Link Target Example',
home: LinkTargetRegion(
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
LinkTargetDetector(
target: 'https://github.com/flutter/flutter',
child: Text('Preview flutter repo'),
),
LinkTargetDetector(
target: 'https://flutter.dev/development',
child: Text('Preview flutter.dev'),
),
LinkTargetDetector(
target: 'https://dart.dev/',
child: Text('Preview dart.dev'),
),
LinkTargetDetector(
target: 'https://www.youtube.com/@flutterdev',
child: Text('Preview Flutter YouTube'),
),
],
),
),
),
),
);
}
}
Loading