Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions .github/workflows/flutter-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,23 @@ jobs:
- uses: subosito/flutter-action@v2
with:
flutter-version: "3.35.2"
- name: Install Linux dependencies
run: sudo apt-get update && sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev
- name: Install Xvfb
run: sudo apt-get install -y xvfb
- run: flutter pub get
- run: |
if [ -d "test" ]; then
flutter test
else
echo "No tests found, skipping..."
fi
- run: |
if [ -d "integration_test" ]; then
xvfb-run -s '-screen 0 1024x768x24' flutter test integration_test -d linux
else
echo "No integration tests found, skipping..."
fi

build:
name: Build
Expand Down
39 changes: 39 additions & 0 deletions integration_test/app_flow_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:flutterproject/main.dart' as app;

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('login then create task', (WidgetTester tester) async {
app.main();

// wait for splash screen to navigate to login
await tester.pumpAndSettle(const Duration(seconds: 4));

// fill login form
await tester.enterText(
find.byType(TextFormField).at(0), 'admin@todolist.com');
await tester.enterText(
find.byType(TextFormField).at(1), '123456');
await tester.tap(find.text('Se connecter'));
await tester.pumpAndSettle();

// wait for tasks to load
await tester.pump(const Duration(seconds: 2));
await tester.pumpAndSettle();

// open form
await tester.tap(find.text('Nouvelle tâche'));
await tester.pumpAndSettle();

// create task
await tester.enterText(
find.byType(TextFormField).first, 'Tâche intégration');
await tester.tap(find.text('Créer'));
await tester.pumpAndSettle();

expect(find.text('Tâche intégration'), findsOneWidget);
});
}
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter

# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
Expand Down
30 changes: 30 additions & 0 deletions test/auth_service_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutterproject/features/auth/data/auth_service.dart';

void main() {
late AuthService service;

setUp(() {
service = AuthService();
});

test('login succeeds with valid credentials', () async {
final result = await service.login('admin@todolist.com', '123456');
expect(result.success, isTrue);
expect(service.isLoggedIn, isTrue);
expect(service.currentUserEmail, 'admin@todolist.com');
});

test('login fails with invalid credentials', () async {
final result = await service.login('wrong@example.com', 'bad');
expect(result.success, isFalse);
expect(service.isLoggedIn, isFalse);
});

test('logout resets state', () async {
await service.login('admin@todolist.com', '123456');
await service.logout();
expect(service.isLoggedIn, isFalse);
expect(service.currentUserEmail, isNull);
});
}
35 changes: 35 additions & 0 deletions test/task_list_widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:flutterproject/features/tasks/domain/models/task.dart';
import 'package:flutterproject/features/tasks/presentation/providers/task_provider.dart';

class _TaskList extends StatelessWidget {
const _TaskList();

@override
Widget build(BuildContext context) {
final tasks = context.watch<TaskProvider>().allTasks;
return ListView(
children: tasks.map((t) => Text(t.title)).toList(),
);
}
}

void main() {
testWidgets('displays tasks from provider', (WidgetTester tester) async {
final provider = TaskProvider();
provider.addTask(Task(id: '1', title: 'Test 1', createdAt: DateTime.now()));
provider.addTask(Task(id: '2', title: 'Test 2', createdAt: DateTime.now()));

await tester.pumpWidget(
ChangeNotifierProvider.value(
value: provider,
child: const MaterialApp(home: Scaffold(body: _TaskList())),
),
);

expect(find.text('Test 1'), findsOneWidget);
expect(find.text('Test 2'), findsOneWidget);
});
}
27 changes: 27 additions & 0 deletions test/task_modal_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:flutterproject/features/tasks/presentation/providers/task_provider.dart';
import 'package:flutterproject/features/tasks/presentation/widgets/task_modal.dart';

void main() {
testWidgets('TaskModal validates empty title', (WidgetTester tester) async {
tester.binding.window.physicalSizeTestValue = const Size(800, 1200);
tester.binding.window.devicePixelRatioTestValue = 1.0;
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
addTearDown(tester.binding.window.clearDevicePixelRatioTestValue);

await tester.pumpWidget(
ChangeNotifierProvider(
create: (_) => TaskProvider(),
child: const MaterialApp(home: Scaffold(body: TaskModal())),
),
);

await tester.ensureVisible(find.text('Créer'));
await tester.tap(find.text('Créer'));
await tester.pump();

expect(find.text('Le titre est obligatoire'), findsOneWidget);
});
}
22 changes: 22 additions & 0 deletions test/task_model_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutterproject/features/tasks/domain/models/task.dart';
import 'package:flutterproject/features/tasks/presentation/providers/task_provider.dart';

void main() {
test('toggleCompleted switches the completion state', () {
final task = Task(
id: '1',
title: 'Demo',
createdAt: DateTime(2024, 1, 1),
isCompleted: false,
);
final toggled = task.toggleCompleted();
expect(toggled.isCompleted, isTrue);
expect(task.isCompleted, isFalse);
});

test('TaskStats calculates completion rate', () {
const stats = TaskStats(total: 4, completed: 1, pending: 3, highPriority: 0);
expect(stats.completionRate, closeTo(0.25, 0.001));
});
}