mirror of
https://github.com/Dvorinka/1356.git
synced 2026-06-05 04:22:55 +00:00
feat: Complete Phase 1 - Full Flutter app implementation with comprehensive features
Version: 1.1.0 Major changes: - Implemented complete Flutter app structure with all core features - Added comprehensive UI screens for auth, countdown, goals, profile, settings, and social features - Integrated Supabase backend with authentication and data repositories - Added offline support with Hive caching and local storage - Implemented comprehensive routing with go_router - Added location services with Google Maps integration - Implemented notifications and home widget support - Added voice recording capabilities and AI chat features - Created comprehensive test suite and documentation - Added Android and iOS platform configurations - Implemented achievements system and social features - Added calendar integration and bucket list functionality This represents a complete Phase 1 milestone with 3,775 additions across 31 files.
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/core/utils/date_time_utils.dart';
|
||||
|
||||
void main() {
|
||||
group('DateTimeUtils', () {
|
||||
group('calculateEndDate', () {
|
||||
test('should calculate end date correctly', () {
|
||||
final startDate = DateTime(2024, 1, 1);
|
||||
final endDate = DateTimeUtils.calculateEndDate(startDate);
|
||||
|
||||
final expectedEndDate = DateTime(2024, 1, 1).add(const Duration(days: 1356));
|
||||
expect(endDate, equals(expectedEndDate));
|
||||
});
|
||||
|
||||
test('should handle leap years correctly', () {
|
||||
final startDate = DateTime(2024, 2, 28); // 2024 is a leap year
|
||||
final endDate = DateTimeUtils.calculateEndDate(startDate);
|
||||
|
||||
final expectedEndDate = startDate.add(const Duration(days: 1356));
|
||||
expect(endDate, equals(expectedEndDate));
|
||||
});
|
||||
|
||||
test('should preserve time component', () {
|
||||
final startDate = DateTime(2024, 1, 1, 12, 30, 45);
|
||||
final endDate = DateTimeUtils.calculateEndDate(startDate);
|
||||
|
||||
final expectedEndDate = DateTime(2024, 1, 1, 12, 30, 45).add(const Duration(days: 1356));
|
||||
expect(endDate, equals(expectedEndDate));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatCountdown', () {
|
||||
test('should format duration with all components', () {
|
||||
const duration = Duration(days: 5, hours: 3, minutes: 45, seconds: 30);
|
||||
final formatted = DateTimeUtils.formatCountdown(duration);
|
||||
|
||||
expect(formatted, equals('5d 3h 45m 30s'));
|
||||
});
|
||||
|
||||
test('should format duration with only days', () {
|
||||
const duration = Duration(days: 10);
|
||||
final formatted = DateTimeUtils.formatCountdown(duration);
|
||||
|
||||
expect(formatted, equals('10d 0h 0m 0s'));
|
||||
});
|
||||
|
||||
test('should format duration with only hours and minutes', () {
|
||||
const duration = Duration(hours: 2, minutes: 30);
|
||||
final formatted = DateTimeUtils.formatCountdown(duration);
|
||||
|
||||
expect(formatted, equals('0d 2h 30m 0s'));
|
||||
});
|
||||
|
||||
test('should format duration with only minutes and seconds', () {
|
||||
const duration = Duration(minutes: 15, seconds: 45);
|
||||
final formatted = DateTimeUtils.formatCountdown(duration);
|
||||
|
||||
expect(formatted, equals('0d 0h 15m 45s'));
|
||||
});
|
||||
|
||||
test('should format zero duration', () {
|
||||
const duration = Duration.zero;
|
||||
final formatted = DateTimeUtils.formatCountdown(duration);
|
||||
|
||||
expect(formatted, equals('0d 0h 0m 0s'));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatCountdownCompact', () {
|
||||
test('should show days and hours when days > 0', () {
|
||||
const duration = Duration(days: 5, hours: 3, minutes: 30);
|
||||
final formatted = DateTimeUtils.formatCountdownCompact(duration);
|
||||
|
||||
expect(formatted, equals('5d 3h'));
|
||||
});
|
||||
|
||||
test('should show hours and minutes when days == 0 and hours > 0', () {
|
||||
const duration = Duration(hours: 3, minutes: 30);
|
||||
final formatted = DateTimeUtils.formatCountdownCompact(duration);
|
||||
|
||||
expect(formatted, equals('3h 30m'));
|
||||
});
|
||||
|
||||
test('should show only minutes when days == 0 and hours == 0', () {
|
||||
const duration = Duration(minutes: 30);
|
||||
final formatted = DateTimeUtils.formatCountdownCompact(duration);
|
||||
|
||||
expect(formatted, equals('30m'));
|
||||
});
|
||||
|
||||
test('should handle zero duration', () {
|
||||
const duration = Duration.zero;
|
||||
final formatted = DateTimeUtils.formatCountdownCompact(duration);
|
||||
|
||||
expect(formatted, equals('0m'));
|
||||
});
|
||||
});
|
||||
|
||||
group('calculateProgress', () {
|
||||
test('should calculate progress correctly', () {
|
||||
final startDate = DateTime(2024, 1, 1);
|
||||
final endDate = DateTime(2024, 1, 11); // 10 days total
|
||||
|
||||
// Mock current time as 5 days after start
|
||||
final progress = DateTimeUtils.calculateProgress(startDate, endDate);
|
||||
|
||||
// Since we can't mock DateTime.now(), we'll just verify the method works
|
||||
expect(progress, greaterThanOrEqualTo(0.0));
|
||||
expect(progress, lessThanOrEqualTo(1.0));
|
||||
});
|
||||
|
||||
test('should return 1.0 when countdown is finished', () {
|
||||
final startDate = DateTime(2024, 1, 1);
|
||||
final endDate = DateTime(2023, 12, 31); // Past date
|
||||
|
||||
final progress = DateTimeUtils.calculateProgress(startDate, endDate);
|
||||
|
||||
expect(progress, equals(1.0));
|
||||
});
|
||||
|
||||
test('should return value between 0 and 1', () {
|
||||
final startDate = DateTime.now().subtract(const Duration(days: 5));
|
||||
final endDate = DateTime.now().add(const Duration(days: 5));
|
||||
|
||||
final progress = DateTimeUtils.calculateProgress(startDate, endDate);
|
||||
|
||||
expect(progress, greaterThan(0.0));
|
||||
expect(progress, lessThan(1.0));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatDate', () {
|
||||
test('should format date correctly', () {
|
||||
final date = DateTime(2024, 1, 15);
|
||||
final formatted = DateTimeUtils.formatDate(date);
|
||||
|
||||
expect(formatted, equals('Jan 15, 2024'));
|
||||
});
|
||||
|
||||
test('should handle different months', () {
|
||||
final date = DateTime(2024, 12, 25);
|
||||
final formatted = DateTimeUtils.formatDate(date);
|
||||
|
||||
expect(formatted, equals('Dec 25, 2024'));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatShortDate', () {
|
||||
test('should format short date correctly', () {
|
||||
final date = DateTime(2024, 1, 15);
|
||||
final formatted = DateTimeUtils.formatShortDate(date);
|
||||
|
||||
expect(formatted, equals('Jan 2024'));
|
||||
});
|
||||
|
||||
test('should handle different years', () {
|
||||
final date = DateTime(2025, 6, 30);
|
||||
final formatted = DateTimeUtils.formatShortDate(date);
|
||||
|
||||
expect(formatted, equals('Jun 2025'));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatDateTime', () {
|
||||
test('should format date and time correctly', () {
|
||||
final dateTime = DateTime(2024, 1, 15, 14, 30);
|
||||
final formatted = DateTimeUtils.formatDateTime(dateTime);
|
||||
|
||||
expect(formatted, equals('Jan 15, 2024 • 14:30'));
|
||||
});
|
||||
});
|
||||
|
||||
group('formatRelativeTime', () {
|
||||
test('should show "Just now" for very recent times', () {
|
||||
final dateTime = DateTime.now().subtract(const Duration(seconds: 30));
|
||||
final formatted = DateTimeUtils.formatRelativeTime(dateTime);
|
||||
|
||||
expect(formatted, equals('Just now'));
|
||||
});
|
||||
|
||||
test('should show minutes for times less than an hour ago', () {
|
||||
final dateTime = DateTime.now().subtract(const Duration(minutes: 30));
|
||||
final formatted = DateTimeUtils.formatRelativeTime(dateTime);
|
||||
|
||||
expect(formatted, equals('30m ago'));
|
||||
});
|
||||
|
||||
test('should show hours for times less than a day ago', () {
|
||||
final dateTime = DateTime.now().subtract(const Duration(hours: 5));
|
||||
final formatted = DateTimeUtils.formatRelativeTime(dateTime);
|
||||
|
||||
expect(formatted, equals('5h ago'));
|
||||
});
|
||||
|
||||
test('should show days for times less than a week ago', () {
|
||||
final dateTime = DateTime.now().subtract(const Duration(days: 3));
|
||||
final formatted = DateTimeUtils.formatRelativeTime(dateTime);
|
||||
|
||||
expect(formatted, equals('3d ago'));
|
||||
});
|
||||
|
||||
test('should show formatted date for times older than a week', () {
|
||||
final dateTime = DateTime(2024, 1, 1);
|
||||
final formatted = DateTimeUtils.formatRelativeTime(dateTime);
|
||||
|
||||
expect(formatted, contains('Jan'));
|
||||
expect(formatted, contains('2024'));
|
||||
});
|
||||
});
|
||||
|
||||
group('isCountdownFinished', () {
|
||||
test('should return true when end date is in the past', () {
|
||||
final endDate = DateTime(2023, 1, 1);
|
||||
final isFinished = DateTimeUtils.isCountdownFinished(endDate);
|
||||
|
||||
expect(isFinished, isTrue);
|
||||
});
|
||||
|
||||
test('should return false when end date is in the future', () {
|
||||
final endDate = DateTime.now().add(const Duration(days: 10));
|
||||
final isFinished = DateTimeUtils.isCountdownFinished(endDate);
|
||||
|
||||
expect(isFinished, isFalse);
|
||||
});
|
||||
|
||||
test('should return true when end date is exactly now', () {
|
||||
final endDate = DateTime.now();
|
||||
final isFinished = DateTimeUtils.isCountdownFinished(endDate);
|
||||
|
||||
// This might be true or false depending on exact timing
|
||||
expect(isFinished, isA<bool>());
|
||||
});
|
||||
});
|
||||
|
||||
group('countdownDays constant', () {
|
||||
test('should be 1356 days', () {
|
||||
expect(DateTimeUtils.countdownDays, equals(1356));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/core/utils/validators.dart';
|
||||
|
||||
void main() {
|
||||
group('Validators', () {
|
||||
group('validateEmail', () {
|
||||
test('should return error for empty email', () {
|
||||
expect(Validators.validateEmail(''), equals('Email is required'));
|
||||
expect(Validators.validateEmail(null), equals('Email is required'));
|
||||
});
|
||||
|
||||
test('should return error for invalid email format', () {
|
||||
expect(Validators.validateEmail('invalid'), equals('Please enter a valid email address'));
|
||||
expect(Validators.validateEmail('invalid@'), equals('Please enter a valid email address'));
|
||||
expect(Validators.validateEmail('@example.com'), equals('Please enter a valid email address'));
|
||||
expect(Validators.validateEmail('test@'), equals('Please enter a valid email address'));
|
||||
});
|
||||
|
||||
test('should return null for valid email', () {
|
||||
expect(Validators.validateEmail('test@example.com'), isNull);
|
||||
expect(Validators.validateEmail('user.name@domain.co.uk'), isNull);
|
||||
expect(Validators.validateEmail('test_user+tag@example.com'), isNull);
|
||||
});
|
||||
|
||||
test('should handle edge cases', () {
|
||||
expect(Validators.validateEmail('a@b.c'), isNull);
|
||||
expect(Validators.validateEmail('test@test.test'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('validatePassword', () {
|
||||
test('should return error for empty password', () {
|
||||
expect(Validators.validatePassword(''), equals('Password must be at least 6 characters'));
|
||||
expect(Validators.validatePassword(null), equals('Password is required'));
|
||||
});
|
||||
|
||||
test('should return error for password less than 6 characters', () {
|
||||
expect(Validators.validatePassword('12345'), equals('Password must be at least 6 characters'));
|
||||
expect(Validators.validatePassword('abc'), equals('Password must be at least 6 characters'));
|
||||
});
|
||||
|
||||
test('should return null for valid password', () {
|
||||
expect(Validators.validatePassword('123456'), isNull);
|
||||
expect(Validators.validatePassword('password'), isNull);
|
||||
expect(Validators.validatePassword('P@ssw0rd!'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('validateUsername', () {
|
||||
test('should return error for empty username', () {
|
||||
expect(Validators.validateUsername(''), equals('Username is required'));
|
||||
expect(Validators.validateUsername(null), equals('Username is required'));
|
||||
});
|
||||
|
||||
test('should return error for username less than 3 characters', () {
|
||||
expect(Validators.validateUsername('ab'), equals('Username must be at least 3 characters'));
|
||||
expect(Validators.validateUsername('a'), equals('Username must be at least 3 characters'));
|
||||
});
|
||||
|
||||
test('should return error for username more than 20 characters', () {
|
||||
expect(Validators.validateUsername('a' * 21), equals('Username must not exceed 20 characters'));
|
||||
});
|
||||
|
||||
test('should return error for username with invalid characters', () {
|
||||
expect(Validators.validateUsername('user name'), equals('Username can only contain letters, numbers, and underscores'));
|
||||
expect(Validators.validateUsername('user-name'), equals('Username can only contain letters, numbers, and underscores'));
|
||||
expect(Validators.validateUsername('user.name'), equals('Username can only contain letters, numbers, and underscores'));
|
||||
expect(Validators.validateUsername('user@name'), equals('Username can only contain letters, numbers, and underscores'));
|
||||
});
|
||||
|
||||
test('should return null for valid username', () {
|
||||
expect(Validators.validateUsername('user'), isNull);
|
||||
expect(Validators.validateUsername('user123'), isNull);
|
||||
expect(Validators.validateUsername('user_name'), isNull);
|
||||
expect(Validators.validateUsername('User_Name_123'), isNull);
|
||||
expect(Validators.validateUsername('a' * 20), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('validateGoalTitle', () {
|
||||
test('should return error for empty title', () {
|
||||
expect(Validators.validateGoalTitle(''), equals('Goal title is required'));
|
||||
expect(Validators.validateGoalTitle(null), equals('Goal title is required'));
|
||||
});
|
||||
|
||||
test('should return error for title more than 100 characters', () {
|
||||
expect(Validators.validateGoalTitle('a' * 101), equals('Goal title must not exceed 100 characters'));
|
||||
});
|
||||
|
||||
test('should return null for valid title', () {
|
||||
expect(Validators.validateGoalTitle('Learn to play guitar'), isNull);
|
||||
expect(Validators.validateGoalTitle('a' * 100), isNull);
|
||||
expect(Validators.validateGoalTitle('Run a marathon'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('validateGoalDescription', () {
|
||||
test('should return null for empty description', () {
|
||||
expect(Validators.validateGoalDescription(''), isNull);
|
||||
expect(Validators.validateGoalDescription(null), isNull);
|
||||
});
|
||||
|
||||
test('should return error for description more than 500 characters', () {
|
||||
expect(Validators.validateGoalDescription('a' * 501), equals('Description must not exceed 500 characters'));
|
||||
});
|
||||
|
||||
test('should return null for valid description', () {
|
||||
expect(Validators.validateGoalDescription('A short description'), isNull);
|
||||
expect(Validators.validateGoalDescription('a' * 500), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('validateGoalProgress', () {
|
||||
test('should return error for null progress', () {
|
||||
expect(Validators.validateGoalProgress(null), equals('Progress is required'));
|
||||
});
|
||||
|
||||
test('should return error for negative progress', () {
|
||||
expect(Validators.validateGoalProgress(-1), equals('Progress must be between 0 and 100'));
|
||||
expect(Validators.validateGoalProgress(-100), equals('Progress must be between 0 and 100'));
|
||||
});
|
||||
|
||||
test('should return error for progress greater than 100', () {
|
||||
expect(Validators.validateGoalProgress(101), equals('Progress must be between 0 and 100'));
|
||||
expect(Validators.validateGoalProgress(150), equals('Progress must be between 0 and 100'));
|
||||
});
|
||||
|
||||
test('should return null for valid progress', () {
|
||||
expect(Validators.validateGoalProgress(0), isNull);
|
||||
expect(Validators.validateGoalProgress(50), isNull);
|
||||
expect(Validators.validateGoalProgress(100), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('validateRequired', () {
|
||||
test('should return error for empty value', () {
|
||||
expect(Validators.validateRequired('', 'Name'), equals('Name is required'));
|
||||
expect(Validators.validateRequired(null, 'Name'), equals('Name is required'));
|
||||
});
|
||||
|
||||
test('should return null for valid value', () {
|
||||
expect(Validators.validateRequired('John', 'Name'), isNull);
|
||||
expect(Validators.validateRequired('123', 'Code'), isNull);
|
||||
});
|
||||
|
||||
test('should use provided field name in error message', () {
|
||||
expect(Validators.validateRequired('', 'Email'), equals('Email is required'));
|
||||
expect(Validators.validateRequired('', 'Password'), equals('Password is required'));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/data/models/activity_model.dart';
|
||||
|
||||
void main() {
|
||||
group('Activity Model', () {
|
||||
group('Constructor and Properties', () {
|
||||
test('should create Activity with required fields', () {
|
||||
final now = DateTime.now();
|
||||
final activity = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_created',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(activity.id, equals('activity-1'));
|
||||
expect(activity.userId, equals('user-1'));
|
||||
expect(activity.type, equals('goal_created'));
|
||||
expect(activity.payload, isNull);
|
||||
});
|
||||
|
||||
test('should create Activity with payload', () {
|
||||
final now = DateTime.now();
|
||||
final payload = {'goal_id': 'goal-1', 'title': 'Test Goal'};
|
||||
final activity = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_completed',
|
||||
payload: payload,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(activity.id, equals('activity-1'));
|
||||
expect(activity.userId, equals('user-1'));
|
||||
expect(activity.type, equals('goal_completed'));
|
||||
expect(activity.payload, equals(payload));
|
||||
});
|
||||
});
|
||||
|
||||
group('copyWith', () {
|
||||
test('should create copy with updated fields', () {
|
||||
final now = DateTime.now();
|
||||
final activity = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_created',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final updatedActivity = activity.copyWith(
|
||||
type: 'goal_completed',
|
||||
payload: const {'goal_id': 'goal-1'},
|
||||
);
|
||||
|
||||
expect(updatedActivity.id, equals(activity.id));
|
||||
expect(updatedActivity.userId, equals(activity.userId));
|
||||
expect(updatedActivity.type, equals('goal_completed'));
|
||||
expect(updatedActivity.payload, equals({'goal_id': 'goal-1'}));
|
||||
});
|
||||
|
||||
test('should preserve original when no fields provided', () {
|
||||
final now = DateTime.now();
|
||||
final activity = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_created',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final copiedActivity = activity.copyWith();
|
||||
|
||||
expect(copiedActivity.id, equals(activity.id));
|
||||
expect(copiedActivity.type, equals(activity.type));
|
||||
expect(copiedActivity.payload, equals(activity.payload));
|
||||
});
|
||||
});
|
||||
|
||||
group('toJson and fromJson', () {
|
||||
test('should serialize to JSON correctly', () {
|
||||
final now = DateTime(2024, 1, 1, 12, 0, 0);
|
||||
final payload = {'goal_id': 'goal-1', 'title': 'Test Goal'};
|
||||
final activity = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_completed',
|
||||
payload: payload,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final json = activity.toJson();
|
||||
|
||||
expect(json['id'], equals('activity-1'));
|
||||
expect(json['user_id'], equals('user-1'));
|
||||
expect(json['type'], equals('goal_completed'));
|
||||
expect(json['payload'], equals(payload));
|
||||
expect(json['created_at'], equals(now.toIso8601String()));
|
||||
});
|
||||
|
||||
test('should deserialize from JSON correctly', () {
|
||||
final now = DateTime(2024, 1, 1, 12, 0, 0);
|
||||
final payload = {'goal_id': 'goal-1', 'title': 'Test Goal'};
|
||||
final json = {
|
||||
'id': 'activity-1',
|
||||
'user_id': 'user-1',
|
||||
'type': 'goal_completed',
|
||||
'payload': payload,
|
||||
'created_at': now.toIso8601String(),
|
||||
};
|
||||
|
||||
final activity = Activity.fromJson(json);
|
||||
|
||||
expect(activity.id, equals('activity-1'));
|
||||
expect(activity.userId, equals('user-1'));
|
||||
expect(activity.type, equals('goal_completed'));
|
||||
expect(activity.payload, equals(payload));
|
||||
});
|
||||
|
||||
test('should handle null payload in JSON', () {
|
||||
final now = DateTime(2024, 1, 1);
|
||||
final json = {
|
||||
'id': 'activity-1',
|
||||
'user_id': 'user-1',
|
||||
'type': 'countdown_started',
|
||||
'payload': null,
|
||||
'created_at': now.toIso8601String(),
|
||||
};
|
||||
|
||||
final activity = Activity.fromJson(json);
|
||||
|
||||
expect(activity.payload, isNull);
|
||||
});
|
||||
|
||||
test('should roundtrip through JSON', () {
|
||||
final payload = {'goal_id': 'goal-1', 'progress': 100};
|
||||
final activity = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_completed',
|
||||
payload: payload,
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
);
|
||||
|
||||
final json = activity.toJson();
|
||||
final deserializedActivity = Activity.fromJson(json);
|
||||
|
||||
expect(deserializedActivity.id, equals(activity.id));
|
||||
expect(deserializedActivity.userId, equals(activity.userId));
|
||||
expect(deserializedActivity.type, equals(activity.type));
|
||||
expect(deserializedActivity.payload, equals(activity.payload));
|
||||
});
|
||||
});
|
||||
|
||||
group('Equatable', () {
|
||||
test('should be equal when all properties match', () {
|
||||
final now = DateTime.now();
|
||||
final activity1 = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_created',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final activity2 = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_created',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(activity1, equals(activity2));
|
||||
expect(activity1.hashCode, equals(activity2.hashCode));
|
||||
});
|
||||
|
||||
test('should not be equal when properties differ', () {
|
||||
final now = DateTime.now();
|
||||
final activity1 = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_created',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final activity2 = Activity(
|
||||
id: 'activity-2',
|
||||
userId: 'user-1',
|
||||
type: 'goal_created',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(activity1, isNot(equals(activity2)));
|
||||
});
|
||||
|
||||
test('should not be equal when type differs', () {
|
||||
final now = DateTime.now();
|
||||
final activity1 = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_created',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final activity2 = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_completed',
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(activity1, isNot(equals(activity2)));
|
||||
});
|
||||
|
||||
test('should not be equal when payload differs', () {
|
||||
final now = DateTime.now();
|
||||
final activity1 = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_completed',
|
||||
payload: const {'goal_id': 'goal-1'},
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final activity2 = Activity(
|
||||
id: 'activity-1',
|
||||
userId: 'user-1',
|
||||
type: 'goal_completed',
|
||||
payload: const {'goal_id': 'goal-2'},
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(activity1, isNot(equals(activity2)));
|
||||
});
|
||||
});
|
||||
|
||||
group('Activity Types', () {
|
||||
test('should support various activity types', () {
|
||||
const types = [
|
||||
'goal_created',
|
||||
'goal_completed',
|
||||
'countdown_started',
|
||||
'countdown_finished',
|
||||
'milestone_reached',
|
||||
'profile_updated',
|
||||
];
|
||||
|
||||
for (final type in types) {
|
||||
final activity = Activity(
|
||||
id: 'activity-$type',
|
||||
userId: 'user-1',
|
||||
type: type,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(activity.type, equals(type));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/data/models/goal_model.dart';
|
||||
|
||||
void main() {
|
||||
group('Goal Model', () {
|
||||
group('Constructor and Properties', () {
|
||||
test('should create Goal with required fields', () {
|
||||
final now = DateTime.now();
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
expect(goal.id, equals('goal-1'));
|
||||
expect(goal.ownerId, equals('user-1'));
|
||||
expect(goal.title, equals('Test Goal'));
|
||||
expect(goal.description, isNull);
|
||||
expect(goal.progress, equals(0));
|
||||
expect(goal.locationLat, isNull);
|
||||
expect(goal.locationLng, isNull);
|
||||
expect(goal.locationName, isNull);
|
||||
expect(goal.imageUrl, isNull);
|
||||
expect(goal.completed, isFalse);
|
||||
});
|
||||
|
||||
test('should create Goal with all fields', () {
|
||||
final now = DateTime.now();
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
description: 'Test description',
|
||||
progress: 50,
|
||||
locationLat: 40.7128,
|
||||
locationLng: -74.0060,
|
||||
locationName: 'New York',
|
||||
imageUrl: 'https://example.com/image.jpg',
|
||||
completed: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
expect(goal.id, equals('goal-1'));
|
||||
expect(goal.ownerId, equals('user-1'));
|
||||
expect(goal.title, equals('Test Goal'));
|
||||
expect(goal.description, equals('Test description'));
|
||||
expect(goal.progress, equals(50));
|
||||
expect(goal.locationLat, equals(40.7128));
|
||||
expect(goal.locationLng, equals(-74.0060));
|
||||
expect(goal.locationName, equals('New York'));
|
||||
expect(goal.imageUrl, equals('https://example.com/image.jpg'));
|
||||
expect(goal.completed, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('Computed Properties', () {
|
||||
test('hasLocation should return false when location is null', () {
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(goal.hasLocation, isFalse);
|
||||
});
|
||||
|
||||
test('hasLocation should return false when only lat is set', () {
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
locationLat: 40.7128,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(goal.hasLocation, isFalse);
|
||||
});
|
||||
|
||||
test('hasLocation should return false when only lng is set', () {
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
locationLng: -74.0060,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(goal.hasLocation, isFalse);
|
||||
});
|
||||
|
||||
test('hasLocation should return true when both lat and lng are set', () {
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
locationLat: 40.7128,
|
||||
locationLng: -74.0060,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(goal.hasLocation, isTrue);
|
||||
});
|
||||
|
||||
test('hasImage should return false when imageUrl is null', () {
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(goal.hasImage, isFalse);
|
||||
});
|
||||
|
||||
test('hasImage should return false when imageUrl is empty', () {
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
imageUrl: '',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(goal.hasImage, isFalse);
|
||||
});
|
||||
|
||||
test('hasImage should return true when imageUrl is set', () {
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
imageUrl: 'https://example.com/image.jpg',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(goal.hasImage, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('copyWith', () {
|
||||
test('should create copy with updated fields', () {
|
||||
final now = DateTime.now();
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
final updatedGoal = goal.copyWith(
|
||||
title: 'Updated Goal',
|
||||
progress: 75,
|
||||
completed: true,
|
||||
);
|
||||
|
||||
expect(updatedGoal.id, equals(goal.id));
|
||||
expect(updatedGoal.ownerId, equals(goal.ownerId));
|
||||
expect(updatedGoal.title, equals('Updated Goal'));
|
||||
expect(updatedGoal.progress, equals(75));
|
||||
expect(updatedGoal.completed, isTrue);
|
||||
});
|
||||
|
||||
test('should preserve original when no fields provided', () {
|
||||
final now = DateTime.now();
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
final copiedGoal = goal.copyWith();
|
||||
|
||||
expect(copiedGoal.id, equals(goal.id));
|
||||
expect(copiedGoal.title, equals(goal.title));
|
||||
expect(copiedGoal.progress, equals(goal.progress));
|
||||
});
|
||||
});
|
||||
|
||||
group('toJson and fromJson', () {
|
||||
test('should serialize to JSON correctly', () {
|
||||
final now = DateTime(2024, 1, 1, 12, 0, 0);
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
description: 'Test description',
|
||||
progress: 50,
|
||||
locationLat: 40.7128,
|
||||
locationLng: -74.0060,
|
||||
locationName: 'New York',
|
||||
imageUrl: 'https://example.com/image.jpg',
|
||||
completed: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
final json = goal.toJson();
|
||||
|
||||
expect(json['id'], equals('goal-1'));
|
||||
expect(json['owner_id'], equals('user-1'));
|
||||
expect(json['title'], equals('Test Goal'));
|
||||
expect(json['description'], equals('Test description'));
|
||||
expect(json['progress'], equals(50));
|
||||
expect(json['location_lat'], equals(40.7128));
|
||||
expect(json['location_lng'], equals(-74.0060));
|
||||
expect(json['location_name'], equals('New York'));
|
||||
expect(json['image_url'], equals('https://example.com/image.jpg'));
|
||||
expect(json['completed'], isFalse);
|
||||
expect(json['created_at'], equals(now.toIso8601String()));
|
||||
expect(json['updated_at'], equals(now.toIso8601String()));
|
||||
});
|
||||
|
||||
test('should deserialize from JSON correctly', () {
|
||||
final now = DateTime(2024, 1, 1, 12, 0, 0);
|
||||
final json = {
|
||||
'id': 'goal-1',
|
||||
'owner_id': 'user-1',
|
||||
'title': 'Test Goal',
|
||||
'description': 'Test description',
|
||||
'progress': 50,
|
||||
'location_lat': 40.7128,
|
||||
'location_lng': -74.0060,
|
||||
'location_name': 'New York',
|
||||
'image_url': 'https://example.com/image.jpg',
|
||||
'completed': false,
|
||||
'created_at': now.toIso8601String(),
|
||||
'updated_at': now.toIso8601String(),
|
||||
};
|
||||
|
||||
final goal = Goal.fromJson(json);
|
||||
|
||||
expect(goal.id, equals('goal-1'));
|
||||
expect(goal.ownerId, equals('user-1'));
|
||||
expect(goal.title, equals('Test Goal'));
|
||||
expect(goal.description, equals('Test description'));
|
||||
expect(goal.progress, equals(50));
|
||||
expect(goal.locationLat, equals(40.7128));
|
||||
expect(goal.locationLng, equals(-74.0060));
|
||||
expect(goal.locationName, equals('New York'));
|
||||
expect(goal.imageUrl, equals('https://example.com/image.jpg'));
|
||||
expect(goal.completed, isFalse);
|
||||
});
|
||||
|
||||
test('should handle null optional fields in JSON', () {
|
||||
final now = DateTime(2024, 1, 1);
|
||||
final json = {
|
||||
'id': 'goal-1',
|
||||
'owner_id': 'user-1',
|
||||
'title': 'Test Goal',
|
||||
'description': null,
|
||||
'progress': null,
|
||||
'location_lat': null,
|
||||
'location_lng': null,
|
||||
'location_name': null,
|
||||
'image_url': null,
|
||||
'completed': null,
|
||||
'created_at': now.toIso8601String(),
|
||||
'updated_at': now.toIso8601String(),
|
||||
};
|
||||
|
||||
final goal = Goal.fromJson(json);
|
||||
|
||||
expect(goal.description, isNull);
|
||||
expect(goal.progress, equals(0));
|
||||
expect(goal.locationLat, isNull);
|
||||
expect(goal.locationLng, isNull);
|
||||
expect(goal.locationName, isNull);
|
||||
expect(goal.imageUrl, isNull);
|
||||
expect(goal.completed, isFalse);
|
||||
});
|
||||
|
||||
test('should roundtrip through JSON', () {
|
||||
final goal = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
description: 'Test description',
|
||||
progress: 50,
|
||||
locationLat: 40.7128,
|
||||
locationLng: -74.0060,
|
||||
locationName: 'New York',
|
||||
imageUrl: 'https://example.com/image.jpg',
|
||||
completed: false,
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
updatedAt: DateTime(2024, 1, 1),
|
||||
);
|
||||
|
||||
final json = goal.toJson();
|
||||
final deserializedGoal = Goal.fromJson(json);
|
||||
|
||||
expect(deserializedGoal.id, equals(goal.id));
|
||||
expect(deserializedGoal.ownerId, equals(goal.ownerId));
|
||||
expect(deserializedGoal.title, equals(goal.title));
|
||||
expect(deserializedGoal.description, equals(goal.description));
|
||||
expect(deserializedGoal.progress, equals(goal.progress));
|
||||
expect(deserializedGoal.locationLat, equals(goal.locationLat));
|
||||
expect(deserializedGoal.locationLng, equals(goal.locationLng));
|
||||
expect(deserializedGoal.locationName, equals(goal.locationName));
|
||||
expect(deserializedGoal.imageUrl, equals(goal.imageUrl));
|
||||
expect(deserializedGoal.completed, equals(goal.completed));
|
||||
});
|
||||
});
|
||||
|
||||
group('Equatable', () {
|
||||
test('should be equal when all properties match', () {
|
||||
final now = DateTime.now();
|
||||
final goal1 = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
final goal2 = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
expect(goal1, equals(goal2));
|
||||
expect(goal1.hashCode, equals(goal2.hashCode));
|
||||
});
|
||||
|
||||
test('should not be equal when properties differ', () {
|
||||
final now = DateTime.now();
|
||||
final goal1 = Goal(
|
||||
id: 'goal-1',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
final goal2 = Goal(
|
||||
id: 'goal-2',
|
||||
ownerId: 'user-1',
|
||||
title: 'Test Goal',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
expect(goal1, isNot(equals(goal2)));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/data/models/goal_step_model.dart';
|
||||
|
||||
void main() {
|
||||
group('GoalStep Model', () {
|
||||
group('Constructor and Properties', () {
|
||||
test('should create GoalStep with required fields', () {
|
||||
final now = DateTime.now();
|
||||
final step = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(step.id, equals('step-1'));
|
||||
expect(step.goalId, equals('goal-1'));
|
||||
expect(step.title, equals('Test Step'));
|
||||
expect(step.isDone, isFalse);
|
||||
expect(step.orderIndex, equals(0));
|
||||
});
|
||||
|
||||
test('should create GoalStep with isDone true', () {
|
||||
final now = DateTime.now();
|
||||
final step = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Completed Step',
|
||||
isDone: true,
|
||||
orderIndex: 1,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(step.id, equals('step-1'));
|
||||
expect(step.goalId, equals('goal-1'));
|
||||
expect(step.title, equals('Completed Step'));
|
||||
expect(step.isDone, isTrue);
|
||||
expect(step.orderIndex, equals(1));
|
||||
});
|
||||
});
|
||||
|
||||
group('copyWith', () {
|
||||
test('should create copy with updated fields', () {
|
||||
final now = DateTime.now();
|
||||
final step = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final updatedStep = step.copyWith(
|
||||
title: 'Updated Step',
|
||||
isDone: true,
|
||||
orderIndex: 1,
|
||||
);
|
||||
|
||||
expect(updatedStep.id, equals(step.id));
|
||||
expect(updatedStep.goalId, equals(step.goalId));
|
||||
expect(updatedStep.title, equals('Updated Step'));
|
||||
expect(updatedStep.isDone, isTrue);
|
||||
expect(updatedStep.orderIndex, equals(1));
|
||||
});
|
||||
|
||||
test('should preserve original when no fields provided', () {
|
||||
final now = DateTime.now();
|
||||
final step = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final copiedStep = step.copyWith();
|
||||
|
||||
expect(copiedStep.id, equals(step.id));
|
||||
expect(copiedStep.title, equals(step.title));
|
||||
expect(copiedStep.isDone, equals(step.isDone));
|
||||
expect(copiedStep.orderIndex, equals(step.orderIndex));
|
||||
});
|
||||
});
|
||||
|
||||
group('toJson and fromJson', () {
|
||||
test('should serialize to JSON correctly', () {
|
||||
final now = DateTime(2024, 1, 1, 12, 0, 0);
|
||||
final step = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final json = step.toJson();
|
||||
|
||||
expect(json['id'], equals('step-1'));
|
||||
expect(json['goal_id'], equals('goal-1'));
|
||||
expect(json['title'], equals('Test Step'));
|
||||
expect(json['is_done'], isFalse);
|
||||
expect(json['order_index'], equals(0));
|
||||
expect(json['created_at'], equals(now.toIso8601String()));
|
||||
});
|
||||
|
||||
test('should deserialize from JSON correctly', () {
|
||||
final now = DateTime(2024, 1, 1, 12, 0, 0);
|
||||
final json = {
|
||||
'id': 'step-1',
|
||||
'goal_id': 'goal-1',
|
||||
'title': 'Test Step',
|
||||
'is_done': false,
|
||||
'order_index': 0,
|
||||
'created_at': now.toIso8601String(),
|
||||
};
|
||||
|
||||
final step = GoalStep.fromJson(json);
|
||||
|
||||
expect(step.id, equals('step-1'));
|
||||
expect(step.goalId, equals('goal-1'));
|
||||
expect(step.title, equals('Test Step'));
|
||||
expect(step.isDone, isFalse);
|
||||
expect(step.orderIndex, equals(0));
|
||||
});
|
||||
|
||||
test('should handle null optional fields in JSON', () {
|
||||
final now = DateTime(2024, 1, 1);
|
||||
final json = {
|
||||
'id': 'step-1',
|
||||
'goal_id': 'goal-1',
|
||||
'title': 'Test Step',
|
||||
'is_done': null,
|
||||
'order_index': null,
|
||||
'created_at': now.toIso8601String(),
|
||||
};
|
||||
|
||||
final step = GoalStep.fromJson(json);
|
||||
|
||||
expect(step.isDone, isFalse);
|
||||
expect(step.orderIndex, equals(0));
|
||||
});
|
||||
|
||||
test('should roundtrip through JSON', () {
|
||||
final step = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: true,
|
||||
orderIndex: 2,
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
);
|
||||
|
||||
final json = step.toJson();
|
||||
final deserializedStep = GoalStep.fromJson(json);
|
||||
|
||||
expect(deserializedStep.id, equals(step.id));
|
||||
expect(deserializedStep.goalId, equals(step.goalId));
|
||||
expect(deserializedStep.title, equals(step.title));
|
||||
expect(deserializedStep.isDone, equals(step.isDone));
|
||||
expect(deserializedStep.orderIndex, equals(step.orderIndex));
|
||||
});
|
||||
});
|
||||
|
||||
group('Equatable', () {
|
||||
test('should be equal when all properties match', () {
|
||||
final now = DateTime.now();
|
||||
final step1 = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final step2 = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(step1, equals(step2));
|
||||
expect(step1.hashCode, equals(step2.hashCode));
|
||||
});
|
||||
|
||||
test('should not be equal when properties differ', () {
|
||||
final now = DateTime.now();
|
||||
final step1 = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final step2 = GoalStep(
|
||||
id: 'step-2',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(step1, isNot(equals(step2)));
|
||||
});
|
||||
|
||||
test('should not be equal when isDone differs', () {
|
||||
final now = DateTime.now();
|
||||
final step1 = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: false,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
final step2 = GoalStep(
|
||||
id: 'step-1',
|
||||
goalId: 'goal-1',
|
||||
title: 'Test Step',
|
||||
isDone: true,
|
||||
orderIndex: 0,
|
||||
createdAt: now,
|
||||
);
|
||||
|
||||
expect(step1, isNot(equals(step2)));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/data/models/user_model.dart';
|
||||
|
||||
void main() {
|
||||
group('User Model', () {
|
||||
group('Constructor and Properties', () {
|
||||
test('should create User with required fields', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
updatedAt: DateTime(2024, 1, 1),
|
||||
);
|
||||
|
||||
expect(user.id, equals('user-1'));
|
||||
expect(user.username, equals('testuser'));
|
||||
expect(user.email, equals('test@example.com'));
|
||||
expect(user.avatarUrl, isNull);
|
||||
expect(user.bio, isNull);
|
||||
expect(user.isPublicProfile, isFalse);
|
||||
expect(user.countdownStartDate, isNull);
|
||||
expect(user.countdownEndDate, isNull);
|
||||
});
|
||||
|
||||
test('should create User with all fields', () {
|
||||
final now = DateTime.now();
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
bio: 'Test bio',
|
||||
isPublicProfile: true,
|
||||
countdownStartDate: now,
|
||||
countdownEndDate: now.add(const Duration(days: 1356)),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
expect(user.id, equals('user-1'));
|
||||
expect(user.username, equals('testuser'));
|
||||
expect(user.email, equals('test@example.com'));
|
||||
expect(user.avatarUrl, equals('https://example.com/avatar.jpg'));
|
||||
expect(user.bio, equals('Test bio'));
|
||||
expect(user.isPublicProfile, isTrue);
|
||||
expect(user.countdownStartDate, equals(now));
|
||||
expect(user.countdownEndDate, equals(now.add(const Duration(days: 1356))));
|
||||
});
|
||||
});
|
||||
|
||||
group('Computed Properties', () {
|
||||
test('hasCountdownStarted should return false when countdownStartDate is null', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(user.hasCountdownStarted, isFalse);
|
||||
});
|
||||
|
||||
test('hasCountdownStarted should return true when countdownStartDate is set', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
countdownStartDate: DateTime.now(),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(user.hasCountdownStarted, isTrue);
|
||||
});
|
||||
|
||||
test('isCountdownActive should return false when countdown not started', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(user.isCountdownActive, isFalse);
|
||||
});
|
||||
|
||||
test('isCountdownActive should return true when countdown is active', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
countdownStartDate: DateTime.now().subtract(const Duration(days: 10)),
|
||||
countdownEndDate: DateTime.now().add(const Duration(days: 1346)),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(user.isCountdownActive, isTrue);
|
||||
});
|
||||
|
||||
test('isCountdownActive should return false when countdown has ended', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
countdownStartDate: DateTime(2023, 1, 1),
|
||||
countdownEndDate: DateTime(2023, 12, 31),
|
||||
createdAt: DateTime(2023, 1, 1),
|
||||
updatedAt: DateTime(2023, 12, 31),
|
||||
);
|
||||
|
||||
expect(user.isCountdownActive, isFalse);
|
||||
});
|
||||
|
||||
test('daysRemaining should return null when countdown not active', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(user.daysRemaining, isNull);
|
||||
});
|
||||
|
||||
test('daysRemaining should return correct days when countdown is active', () {
|
||||
final endDate = DateTime.now().add(const Duration(days: 100));
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
countdownStartDate: DateTime.now(),
|
||||
countdownEndDate: endDate,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
final daysRemaining = user.daysRemaining;
|
||||
expect(daysRemaining, isNotNull);
|
||||
expect(daysRemaining, greaterThan(0));
|
||||
expect(daysRemaining, lessThanOrEqualTo(100));
|
||||
});
|
||||
});
|
||||
|
||||
group('copyWith', () {
|
||||
test('should create copy with updated fields', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
final updatedUser = user.copyWith(
|
||||
username: 'newuser',
|
||||
bio: 'New bio',
|
||||
);
|
||||
|
||||
expect(updatedUser.id, equals(user.id));
|
||||
expect(updatedUser.username, equals('newuser'));
|
||||
expect(updatedUser.email, equals(user.email));
|
||||
expect(updatedUser.bio, equals('New bio'));
|
||||
});
|
||||
|
||||
test('should preserve original when no fields provided', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
final copiedUser = user.copyWith();
|
||||
|
||||
expect(copiedUser.id, equals(user.id));
|
||||
expect(copiedUser.username, equals(user.username));
|
||||
expect(copiedUser.email, equals(user.email));
|
||||
});
|
||||
});
|
||||
|
||||
group('toJson and fromJson', () {
|
||||
test('should serialize to JSON correctly', () {
|
||||
final now = DateTime(2024, 1, 1, 12, 0, 0);
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
bio: 'Test bio',
|
||||
isPublicProfile: true,
|
||||
countdownStartDate: now,
|
||||
countdownEndDate: now.add(const Duration(days: 1356)),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
final json = user.toJson();
|
||||
|
||||
expect(json['id'], equals('user-1'));
|
||||
expect(json['username'], equals('testuser'));
|
||||
expect(json['email'], equals('test@example.com'));
|
||||
expect(json['avatar_url'], equals('https://example.com/avatar.jpg'));
|
||||
expect(json['bio'], equals('Test bio'));
|
||||
expect(json['is_public_profile'], isTrue);
|
||||
expect(json['countdown_start_date'], equals(now.toIso8601String()));
|
||||
expect(json['countdown_end_date'], equals(now.add(const Duration(days: 1356)).toIso8601String()));
|
||||
expect(json['created_at'], equals(now.toIso8601String()));
|
||||
expect(json['updated_at'], equals(now.toIso8601String()));
|
||||
});
|
||||
|
||||
test('should deserialize from JSON correctly', () {
|
||||
final now = DateTime(2024, 1, 1, 12, 0, 0);
|
||||
final json = {
|
||||
'id': 'user-1',
|
||||
'username': 'testuser',
|
||||
'email': 'test@example.com',
|
||||
'avatar_url': 'https://example.com/avatar.jpg',
|
||||
'bio': 'Test bio',
|
||||
'is_public_profile': true,
|
||||
'countdown_start_date': now.toIso8601String(),
|
||||
'countdown_end_date': now.add(const Duration(days: 1356)).toIso8601String(),
|
||||
'created_at': now.toIso8601String(),
|
||||
'updated_at': now.toIso8601String(),
|
||||
};
|
||||
|
||||
final user = User.fromJson(json);
|
||||
|
||||
expect(user.id, equals('user-1'));
|
||||
expect(user.username, equals('testuser'));
|
||||
expect(user.email, equals('test@example.com'));
|
||||
expect(user.avatarUrl, equals('https://example.com/avatar.jpg'));
|
||||
expect(user.bio, equals('Test bio'));
|
||||
expect(user.isPublicProfile, isTrue);
|
||||
expect(user.countdownStartDate, equals(now));
|
||||
expect(user.countdownEndDate, equals(now.add(const Duration(days: 1356))));
|
||||
});
|
||||
|
||||
test('should handle null optional fields in JSON', () {
|
||||
final now = DateTime(2024, 1, 1);
|
||||
final json = {
|
||||
'id': 'user-1',
|
||||
'username': 'testuser',
|
||||
'email': 'test@example.com',
|
||||
'avatar_url': null,
|
||||
'bio': null,
|
||||
'is_public_profile': null,
|
||||
'countdown_start_date': null,
|
||||
'countdown_end_date': null,
|
||||
'created_at': now.toIso8601String(),
|
||||
'updated_at': now.toIso8601String(),
|
||||
};
|
||||
|
||||
final user = User.fromJson(json);
|
||||
|
||||
expect(user.avatarUrl, isNull);
|
||||
expect(user.bio, isNull);
|
||||
expect(user.isPublicProfile, isFalse);
|
||||
expect(user.countdownStartDate, isNull);
|
||||
expect(user.countdownEndDate, isNull);
|
||||
});
|
||||
|
||||
test('should roundtrip through JSON', () {
|
||||
final user = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
bio: 'Test bio',
|
||||
isPublicProfile: true,
|
||||
countdownStartDate: DateTime(2024, 1, 1),
|
||||
countdownEndDate: DateTime(2024, 1, 1).add(const Duration(days: 1356)),
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
updatedAt: DateTime(2024, 1, 1),
|
||||
);
|
||||
|
||||
final json = user.toJson();
|
||||
final deserializedUser = User.fromJson(json);
|
||||
|
||||
expect(deserializedUser.id, equals(user.id));
|
||||
expect(deserializedUser.username, equals(user.username));
|
||||
expect(deserializedUser.email, equals(user.email));
|
||||
expect(deserializedUser.avatarUrl, equals(user.avatarUrl));
|
||||
expect(deserializedUser.bio, equals(user.bio));
|
||||
expect(deserializedUser.isPublicProfile, equals(user.isPublicProfile));
|
||||
expect(deserializedUser.countdownStartDate, equals(user.countdownStartDate));
|
||||
expect(deserializedUser.countdownEndDate, equals(user.countdownEndDate));
|
||||
});
|
||||
});
|
||||
|
||||
group('Equatable', () {
|
||||
test('should be equal when all properties match', () {
|
||||
final now = DateTime.now();
|
||||
final user1 = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
final user2 = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
expect(user1, equals(user2));
|
||||
expect(user1.hashCode, equals(user2.hashCode));
|
||||
});
|
||||
|
||||
test('should not be equal when properties differ', () {
|
||||
final now = DateTime.now();
|
||||
final user1 = User(
|
||||
id: 'user-1',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
final user2 = User(
|
||||
id: 'user-2',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
|
||||
expect(user1, isNot(equals(user2)));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/auth/application/auth_controller.dart';
|
||||
import 'package:lifetimer/features/auth/presentation/auth_gate.dart';
|
||||
import 'package:lifetimer/features/auth/presentation/auth_choice_screen.dart';
|
||||
import 'package:lifetimer/features/onboarding/presentation/onboarding_intro_screen.dart';
|
||||
import 'package:lifetimer/data/models/user_model.dart';
|
||||
import 'package:lifetimer/data/repositories/auth_repository.dart';
|
||||
|
||||
class MockAuthRepository extends AuthRepository {
|
||||
bool _isAuthenticated = false;
|
||||
|
||||
MockAuthRepository() : super(null);
|
||||
|
||||
@override
|
||||
User? get currentUser => _isAuthenticated ? TestData.createTestUser() : null;
|
||||
|
||||
@override
|
||||
Stream<User?> get authStateChanges => Stream.value(currentUser);
|
||||
|
||||
@override
|
||||
bool get isAuthenticated => _isAuthenticated;
|
||||
|
||||
@override
|
||||
String? get currentUserId => currentUser?.id;
|
||||
|
||||
@override
|
||||
Future<void> signInWithEmail(String email, String password) async {}
|
||||
|
||||
@override
|
||||
Future<void> signUpWithEmail(String email, String password, String username) async {}
|
||||
|
||||
@override
|
||||
Future<void> signInWithGoogle() async {}
|
||||
|
||||
@override
|
||||
Future<void> signInWithApple() async {}
|
||||
|
||||
@override
|
||||
Future<void> signOut() async {}
|
||||
|
||||
@override
|
||||
Future<void> resetPassword(String email) async {}
|
||||
|
||||
@override
|
||||
Future<bool> isSessionValid() async => true;
|
||||
|
||||
@override
|
||||
Future<void> refreshSession() async {}
|
||||
|
||||
@override
|
||||
Future<void> updateProfile({
|
||||
String? username,
|
||||
String? bio,
|
||||
String? avatarUrl,
|
||||
bool? isPublicProfile,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class TestData {
|
||||
static User createTestUser() {
|
||||
return User(
|
||||
id: 'test-user-id',
|
||||
username: 'testuser',
|
||||
email: 'test@example.com',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('AuthGate Widget', () {
|
||||
testWidgets('should show AuthChoiceScreen when user is not authenticated',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
authRepositoryProvider.overrideWithValue(MockAuthRepository()),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
home: AuthGate(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(AuthChoiceScreen), findsOneWidget);
|
||||
expect(find.byType(OnboardingIntroScreen), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should show OnboardingIntroScreen when user is authenticated',
|
||||
(WidgetTester tester) async {
|
||||
final mockRepo = MockAuthRepository();
|
||||
mockRepo._isAuthenticated = true;
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
authRepositoryProvider.overrideWithValue(mockRepo),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
home: AuthGate(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(OnboardingIntroScreen), findsOneWidget);
|
||||
expect(find.byType(AuthChoiceScreen), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/auth/presentation/sign_in_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('SignInScreen Widget', () {
|
||||
testWidgets('should display email and password fields',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: SignInScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Welcome back'), findsOneWidget);
|
||||
expect(find.text('Sign in to continue your journey'), findsOneWidget);
|
||||
expect(find.byType(TextFormField), findsNWidgets(2));
|
||||
expect(find.text('Email'), findsOneWidget);
|
||||
expect(find.text('Password'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show sign in button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: SignInScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Sign In'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show forgot password button',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignInScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Forgot password?'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show sign up link', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignInScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text("Don't have an account?"), findsOneWidget);
|
||||
expect(find.text('Sign Up'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should validate email field', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignInScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find email field
|
||||
final emailField = find.widgetWithText(TextFormField, 'Email');
|
||||
await tester.enterText(emailField, 'invalid-email');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Try to submit
|
||||
final signInButton = find.text('Sign In');
|
||||
await tester.tap(signInButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show validation error
|
||||
expect(find.text('Please enter a valid email address'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should validate password field', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignInScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find password field
|
||||
final passwordField = find.widgetWithText(TextFormField, 'Password');
|
||||
await tester.enterText(passwordField, '123');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Try to submit
|
||||
final signInButton = find.text('Sign In');
|
||||
await tester.tap(signInButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show validation error
|
||||
expect(find.text('Password must be at least 6 characters'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should toggle password visibility',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignInScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find password visibility toggle button
|
||||
final toggleButton = find.byIcon(Icons.visibility_off);
|
||||
expect(toggleButton, findsOneWidget);
|
||||
|
||||
await tester.tap(toggleButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should now show visibility icon
|
||||
expect(find.byIcon(Icons.visibility), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/auth/presentation/sign_up_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('SignUpScreen Widget', () {
|
||||
testWidgets('should display username, email, and password fields',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Create your account'), findsOneWidget);
|
||||
expect(find.text('Start your 1356-day journey'), findsOneWidget);
|
||||
expect(find.byType(TextFormField), findsNWidgets(3));
|
||||
expect(find.text('Username'), findsOneWidget);
|
||||
expect(find.text('Email'), findsOneWidget);
|
||||
expect(find.text('Password'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show sign up button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Sign Up'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show sign in link', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Already have an account?'), findsOneWidget);
|
||||
expect(find.text('Sign In'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should validate username field', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find username field
|
||||
final usernameField = find.widgetWithText(TextFormField, 'Username');
|
||||
await tester.enterText(usernameField, 'ab');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Try to submit
|
||||
final signUpButton = find.text('Sign Up');
|
||||
await tester.tap(signUpButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show validation error
|
||||
expect(find.text('Username must be at least 3 characters'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should validate email field', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find email field
|
||||
final emailField = find.widgetWithText(TextFormField, 'Email');
|
||||
await tester.enterText(emailField, 'invalid-email');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Try to submit
|
||||
final signUpButton = find.text('Sign Up');
|
||||
await tester.tap(signUpButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show validation error
|
||||
expect(find.text('Please enter a valid email address'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should validate password field', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find password field
|
||||
final passwordField = find.widgetWithText(TextFormField, 'Password');
|
||||
await tester.enterText(passwordField, '123');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Try to submit
|
||||
final signUpButton = find.text('Sign Up');
|
||||
await tester.tap(signUpButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show validation error
|
||||
expect(find.text('Password must be at least 6 characters'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should toggle password visibility',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find password visibility toggle button
|
||||
final toggleButton = find.byIcon(Icons.visibility_off);
|
||||
expect(toggleButton, findsOneWidget);
|
||||
|
||||
await tester.tap(toggleButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should now show visibility icon
|
||||
expect(find.byIcon(Icons.visibility), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show Google sign up button',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Sign up with Google'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show Apple sign up button',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SignUpScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Sign up with Apple'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/countdown/presentation/bucket_list_confirmation_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('BucketListConfirmationScreen Widget', () {
|
||||
testWidgets('should display confirmation title', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: BucketListConfirmationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Finalize Your Bucket List'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display goals count', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: BucketListConfirmationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('goals'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display warning message', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: BucketListConfirmationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('cannot be paused'), findsOneWidget);
|
||||
expect(find.textContaining('cannot be reset'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display start countdown button',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: BucketListConfirmationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Start 1356-Day Challenge'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display review goals button',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: BucketListConfirmationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Review Goals'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display countdown duration info',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: BucketListConfirmationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('1356'), findsOneWidget);
|
||||
expect(find.textContaining('years'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/countdown/presentation/home_countdown_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('HomeCountdownScreen Widget', () {
|
||||
testWidgets('should display countdown timer', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: HomeCountdownScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should display countdown components
|
||||
expect(find.byType(Scaffold), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display days remaining', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: HomeCountdownScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should display large countdown display
|
||||
expect(find.textContaining('days'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display progress indicator', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: HomeCountdownScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should have progress visualization
|
||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display motivational message',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: HomeCountdownScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show motivational text
|
||||
expect(find.textContaining('Make every day count'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display view goals button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: HomeCountdownScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('View My Goals'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display hours, minutes, seconds breakdown',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: HomeCountdownScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should display time breakdown
|
||||
expect(find.textContaining('h'), findsOneWidget);
|
||||
expect(find.textContaining('m'), findsOneWidget);
|
||||
expect(find.textContaining('s'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display percentage completed',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: HomeCountdownScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('%'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/goals/presentation/goal_detail_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('GoalDetailScreen Widget', () {
|
||||
testWidgets('should display goal detail title', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalDetailScreen(goalId: 'test-goal-id'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should display goal detail view
|
||||
expect(find.byType(Scaffold), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display progress slider', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalDetailScreen(goalId: 'test-goal-id'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should have progress controls
|
||||
expect(find.byType(Slider), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display mark as completed button',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalDetailScreen(goalId: 'test-goal-id'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Mark as Completed'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display edit button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalDetailScreen(goalId: 'test-goal-id'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byIcon(Icons.edit), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display delete button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalDetailScreen(goalId: 'test-goal-id'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byIcon(Icons.delete), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display milestones list', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalDetailScreen(goalId: 'test-goal-id'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Milestones'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display progress percentage',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalDetailScreen(goalId: 'test-goal-id'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('%'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/goals/presentation/goal_edit_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('GoalEditScreen Widget', () {
|
||||
testWidgets('should display goal edit title', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Add Goal'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display title field', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Title'), findsOneWidget);
|
||||
expect(find.byType(TextFormField), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('should display description field', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Description'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display save button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Save Goal'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display cancel button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Cancel'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should validate title field', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Try to save without title
|
||||
final saveButton = find.text('Save Goal');
|
||||
await tester.tap(saveButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show validation error
|
||||
expect(find.text('Goal title is required'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display location picker', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Add Location'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display image picker', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Add Image'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display milestones section',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalEditScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Milestones'), findsOneWidget);
|
||||
expect(find.text('Add Milestone'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/goals/presentation/goals_list_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('GoalsListScreen Widget', () {
|
||||
testWidgets('should display goals list title', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalsListScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('My Goals'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display add goal button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalsListScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Add Goal'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display goals counter', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalsListScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('/20'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display empty state when no goals',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalsListScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show empty state message
|
||||
expect(find.textContaining('No goals'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display start countdown button when goals exist',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: GoalsListScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The button might not be visible until goals are added
|
||||
// This test verifies the structure is in place
|
||||
expect(find.byType(FloatingActionButton), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/onboarding/presentation/onboarding_how_it_works_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('OnboardingHowItWorksScreen Widget', () {
|
||||
testWidgets('should display how it works title', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingHowItWorksScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('How It Works'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display bucket list step', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingHowItWorksScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('bucket list'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display countdown step', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingHowItWorksScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('countdown'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display next button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingHowItWorksScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Next'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display back button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingHowItWorksScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display step indicators', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingHowItWorksScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should have step indicators
|
||||
expect(find.byType(Container), findsWidgets);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/onboarding/presentation/onboarding_intro_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('OnboardingIntroScreen Widget', () {
|
||||
testWidgets('should display welcome message', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingIntroScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Welcome to LifeTimer'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display 1356-day challenge description',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingIntroScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('1356'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display get started button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingIntroScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Get Started'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display skip button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingIntroScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Skip'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display page indicator', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingIntroScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should have page indicators (dots)
|
||||
expect(find.byType(Container), findsWidgets);
|
||||
});
|
||||
});
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/onboarding/presentation/onboarding_motivation_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('OnboardingMotivationScreen Widget', () {
|
||||
testWidgets('should display motivation title', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingMotivationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Your Journey Awaits'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display motivational message',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingMotivationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('goals'), findsOneWidget);
|
||||
expect(find.textContaining('dreams'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display start challenge button',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingMotivationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Start Your Challenge'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display back button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingMotivationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Back'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display step indicators', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: OnboardingMotivationScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should have step indicators
|
||||
expect(find.byType(Container), findsWidgets);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/profile/presentation/profile_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('ProfileScreen Widget', () {
|
||||
testWidgets('should display profile title', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: ProfileScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Profile'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display user avatar', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: ProfileScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(CircleAvatar), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display username', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: ProfileScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should display username section
|
||||
expect(find.textContaining('Username'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display countdown information',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: ProfileScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Days Left'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display goals completed stat',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: ProfileScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Goals Completed'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display edit profile button',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: ProfileScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Edit Profile'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display settings button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: ProfileScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Settings'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display sign out button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: ProfileScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Sign Out'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/settings/presentation/about_challenge_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('AboutChallengeScreen Widget', () {
|
||||
testWidgets('should display about challenge title',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: AboutChallengeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('About the 1356-Day Challenge'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display challenge description',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: AboutChallengeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('1356'), findsOneWidget);
|
||||
expect(find.textContaining('days'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display bucket list explanation',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: AboutChallengeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('bucket list'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display countdown rules', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: AboutChallengeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('cannot be paused'), findsOneWidget);
|
||||
expect(find.textContaining('cannot be reset'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display motivation section', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: AboutChallengeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Make every day count'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display close button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: AboutChallengeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Close'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/settings/presentation/notification_settings_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('NotificationSettingsScreen Widget', () {
|
||||
testWidgets('should display notification settings title',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: NotificationSettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Notification Settings'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display daily reminder option',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: NotificationSettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Daily Reminder'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display weekly reminder option',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: NotificationSettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Weekly Reminder'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display milestone notifications option',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: NotificationSettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Milestone Notifications'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display countdown checkpoint notifications',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: NotificationSettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Countdown Checkpoints'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display toggle switches', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: NotificationSettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(Switch), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('should display save button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: NotificationSettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Save'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/settings/presentation/privacy_settings_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('PrivacySettingsScreen Widget', () {
|
||||
testWidgets('should display privacy settings title',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: PrivacySettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Privacy Settings'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display profile visibility toggle',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: PrivacySettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Public Profile'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display visibility description',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: PrivacySettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Make your profile visible'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display private profile description',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: PrivacySettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Only you can see'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display public profile description',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: PrivacySettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Others can see'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display visibility toggle switch',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: PrivacySettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(Switch), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display save button', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: PrivacySettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Save'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// ignore_for_file: unnecessary_const
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lifetimer/features/settings/presentation/settings_home_screen.dart';
|
||||
|
||||
void main() {
|
||||
group('SettingsHomeScreen Widget', () {
|
||||
testWidgets('should display settings title', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Settings'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display account section', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Account'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display preferences section',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Preferences'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display privacy section', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Privacy'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display about section', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('About'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display account settings option',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Account Settings'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display notification settings option',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Notifications'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display privacy settings option',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Privacy Settings'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display about challenge option',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: const MaterialApp(
|
||||
home: SettingsHomeScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('About the Challenge'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'package:lifetimer/data/repositories/auth_repository.dart';
|
||||
import 'package:lifetimer/data/repositories/goals_repository.dart';
|
||||
import 'package:lifetimer/data/repositories/countdown_repository.dart';
|
||||
import 'package:lifetimer/data/repositories/user_repository.dart';
|
||||
import 'package:lifetimer/data/repositories/social_repository.dart';
|
||||
import 'package:lifetimer/data/repositories/notifications_repository.dart';
|
||||
import 'package:lifetimer/features/auth/application/auth_controller.dart';
|
||||
import 'package:lifetimer/features/goals/application/goals_controller.dart';
|
||||
import 'package:lifetimer/features/countdown/application/countdown_controller.dart';
|
||||
import 'package:lifetimer/features/settings/application/settings_controller.dart';
|
||||
import 'package:lifetimer/features/social/application/social_controller.dart';
|
||||
|
||||
// Note: Run 'flutter pub run build_runner build' to generate mocks
|
||||
@GenerateMocks([
|
||||
AuthRepository,
|
||||
GoalsRepository,
|
||||
CountdownRepository,
|
||||
UserRepository,
|
||||
SocialRepository,
|
||||
NotificationsRepository,
|
||||
])
|
||||
import 'mock_providers.mocks.dart';
|
||||
|
||||
/// Helper to create mock repositories for testing
|
||||
class MockRepositories {
|
||||
late MockAuthRepository authRepository;
|
||||
late MockGoalsRepository goalsRepository;
|
||||
late MockCountdownRepository countdownRepository;
|
||||
late MockUserRepository userRepository;
|
||||
late MockSocialRepository socialRepository;
|
||||
late MockNotificationsRepository notificationsRepository;
|
||||
|
||||
MockRepositories() {
|
||||
authRepository = MockAuthRepository();
|
||||
goalsRepository = MockGoalsRepository();
|
||||
countdownRepository = MockCountdownRepository();
|
||||
userRepository = MockUserRepository();
|
||||
socialRepository = MockSocialRepository();
|
||||
notificationsRepository = MockNotificationsRepository();
|
||||
}
|
||||
|
||||
/// Get all repository overrides
|
||||
List<Override> get overrides => [
|
||||
authRepositoryProvider.overrideWithValue(authRepository),
|
||||
goalsRepositoryProvider.overrideWithValue(goalsRepository),
|
||||
countdownRepositoryProvider.overrideWithValue(countdownRepository),
|
||||
userRepositoryProvider.overrideWithValue(userRepository),
|
||||
socialRepositoryProvider.overrideWithValue(socialRepository),
|
||||
notificationsRepositoryProvider.overrideWithValue(notificationsRepository),
|
||||
];
|
||||
}
|
||||
|
||||
/// Helper to create a mock Supabase client
|
||||
class MockSupabaseClient extends Mock implements SupabaseClient {}
|
||||
|
||||
/// Helper to create a mock Supabase auth
|
||||
class MockSupabaseAuth extends Mock implements GoTrueClient {}
|
||||
|
||||
/// Helper to create a mock Supabase database
|
||||
class MockSupabaseDatabase extends Mock implements PostgrestClient {}
|
||||
@@ -0,0 +1,781 @@
|
||||
// Mocks generated by Mockito 5.4.4 from annotations
|
||||
// in lifetimer/test/helpers/mock_providers.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i7;
|
||||
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
|
||||
as _i14;
|
||||
import 'package:lifetimer/data/models/activity_model.dart' as _i5;
|
||||
import 'package:lifetimer/data/models/goal_model.dart' as _i2;
|
||||
import 'package:lifetimer/data/models/goal_step_model.dart' as _i3;
|
||||
import 'package:lifetimer/data/models/user_model.dart' as _i4;
|
||||
import 'package:lifetimer/data/repositories/auth_repository.dart' as _i6;
|
||||
import 'package:lifetimer/data/repositories/countdown_repository.dart' as _i10;
|
||||
import 'package:lifetimer/data/repositories/goals_repository.dart' as _i9;
|
||||
import 'package:lifetimer/data/repositories/notifications_repository.dart'
|
||||
as _i13;
|
||||
import 'package:lifetimer/data/repositories/social_repository.dart' as _i12;
|
||||
import 'package:lifetimer/data/repositories/user_repository.dart' as _i11;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:supabase_flutter/supabase_flutter.dart' as _i8;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
|
||||
class _FakeGoal_0 extends _i1.SmartFake implements _i2.Goal {
|
||||
_FakeGoal_0(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeGoalStep_1 extends _i1.SmartFake implements _i3.GoalStep {
|
||||
_FakeGoalStep_1(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeUser_2 extends _i1.SmartFake implements _i4.User {
|
||||
_FakeUser_2(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeActivity_3 extends _i1.SmartFake implements _i5.Activity {
|
||||
_FakeActivity_3(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [AuthRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockAuthRepository extends _i1.Mock implements _i6.AuthRepository {
|
||||
MockAuthRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i7.Stream<_i4.User?> get authStateChanges => (super.noSuchMethod(
|
||||
Invocation.getter(#authStateChanges),
|
||||
returnValue: _i7.Stream<_i4.User?>.empty(),
|
||||
) as _i7.Stream<_i4.User?>);
|
||||
|
||||
@override
|
||||
bool get isAuthenticated => (super.noSuchMethod(
|
||||
Invocation.getter(#isAuthenticated),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
|
||||
@override
|
||||
_i7.Future<bool> isSessionValid() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#isSessionValid,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<bool>.value(false),
|
||||
) as _i7.Future<bool>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> refreshSession() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#refreshSession,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i8.Session?> getCurrentSession() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getCurrentSession,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<_i8.Session?>.value(),
|
||||
) as _i7.Future<_i8.Session?>);
|
||||
|
||||
@override
|
||||
void listenToAuthStateChanges(dynamic Function(_i4.User?)? callback) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#listenToAuthStateChanges,
|
||||
[callback],
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#dispose,
|
||||
[],
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i7.Future<void> signInWithEmail(
|
||||
String? email,
|
||||
String? password,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#signInWithEmail,
|
||||
[
|
||||
email,
|
||||
password,
|
||||
],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> signUpWithEmail(
|
||||
String? email,
|
||||
String? password,
|
||||
String? username,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#signUpWithEmail,
|
||||
[
|
||||
email,
|
||||
password,
|
||||
username,
|
||||
],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> signInWithGoogle() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#signInWithGoogle,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> signInWithGithub() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#signInWithGithub,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> signInWithApple() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#signInWithApple,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> signOut() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#signOut,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> resetPassword(String? email) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#resetPassword,
|
||||
[email],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> updateProfile({
|
||||
String? username,
|
||||
String? bio,
|
||||
String? avatarUrl,
|
||||
bool? isPublicProfile,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#updateProfile,
|
||||
[],
|
||||
{
|
||||
#username: username,
|
||||
#bio: bio,
|
||||
#avatarUrl: avatarUrl,
|
||||
#isPublicProfile: isPublicProfile,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [GoalsRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockGoalsRepository extends _i1.Mock implements _i9.GoalsRepository {
|
||||
MockGoalsRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i7.Future<List<_i2.Goal>> getGoals(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getGoals,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<List<_i2.Goal>>.value(<_i2.Goal>[]),
|
||||
) as _i7.Future<List<_i2.Goal>>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i2.Goal> getGoal(String? goalId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getGoal,
|
||||
[goalId],
|
||||
),
|
||||
returnValue: _i7.Future<_i2.Goal>.value(_FakeGoal_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getGoal,
|
||||
[goalId],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i2.Goal>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i2.Goal> createGoal(_i2.Goal? goal) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#createGoal,
|
||||
[goal],
|
||||
),
|
||||
returnValue: _i7.Future<_i2.Goal>.value(_FakeGoal_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#createGoal,
|
||||
[goal],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i2.Goal>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i2.Goal> updateGoal(_i2.Goal? goal) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#updateGoal,
|
||||
[goal],
|
||||
),
|
||||
returnValue: _i7.Future<_i2.Goal>.value(_FakeGoal_0(
|
||||
this,
|
||||
Invocation.method(
|
||||
#updateGoal,
|
||||
[goal],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i2.Goal>);
|
||||
|
||||
@override
|
||||
_i7.Future<bool> canModifyGoals(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#canModifyGoals,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<bool>.value(false),
|
||||
) as _i7.Future<bool>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> deleteGoal(String? goalId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#deleteGoal,
|
||||
[goalId],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<List<_i3.GoalStep>> getGoalSteps(String? goalId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getGoalSteps,
|
||||
[goalId],
|
||||
),
|
||||
returnValue: _i7.Future<List<_i3.GoalStep>>.value(<_i3.GoalStep>[]),
|
||||
) as _i7.Future<List<_i3.GoalStep>>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i3.GoalStep> createGoalStep(_i3.GoalStep? step) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#createGoalStep,
|
||||
[step],
|
||||
),
|
||||
returnValue: _i7.Future<_i3.GoalStep>.value(_FakeGoalStep_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#createGoalStep,
|
||||
[step],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.GoalStep>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i3.GoalStep> updateGoalStep(_i3.GoalStep? step) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#updateGoalStep,
|
||||
[step],
|
||||
),
|
||||
returnValue: _i7.Future<_i3.GoalStep>.value(_FakeGoalStep_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#updateGoalStep,
|
||||
[step],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.GoalStep>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> deleteGoalStep(String? stepId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#deleteGoalStep,
|
||||
[stepId],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<int> getGoalsCount(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getGoalsCount,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<int>.value(0),
|
||||
) as _i7.Future<int>);
|
||||
}
|
||||
|
||||
/// A class which mocks [CountdownRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockCountdownRepository extends _i1.Mock
|
||||
implements _i10.CountdownRepository {
|
||||
MockCountdownRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i7.Future<_i4.User> startCountdown(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#startCountdown,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<_i4.User>.value(_FakeUser_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#startCountdown,
|
||||
[userId],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i4.User>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i4.User> getCountdownInfo(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getCountdownInfo,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<_i4.User>.value(_FakeUser_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getCountdownInfo,
|
||||
[userId],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i4.User>);
|
||||
|
||||
@override
|
||||
_i7.Future<bool> hasCountdownStarted(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#hasCountdownStarted,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<bool>.value(false),
|
||||
) as _i7.Future<bool>);
|
||||
}
|
||||
|
||||
/// A class which mocks [UserRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockUserRepository extends _i1.Mock implements _i11.UserRepository {
|
||||
MockUserRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i7.Future<_i4.User> getProfile(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getProfile,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<_i4.User>.value(_FakeUser_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getProfile,
|
||||
[userId],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i4.User>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i4.User> updateProfile({
|
||||
required String? userId,
|
||||
String? username,
|
||||
String? avatarUrl,
|
||||
String? bio,
|
||||
bool? isPublicProfile,
|
||||
String? twitterHandle,
|
||||
String? instagramHandle,
|
||||
String? tiktokHandle,
|
||||
String? websiteUrl,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#updateProfile,
|
||||
[],
|
||||
{
|
||||
#userId: userId,
|
||||
#username: username,
|
||||
#avatarUrl: avatarUrl,
|
||||
#bio: bio,
|
||||
#isPublicProfile: isPublicProfile,
|
||||
#twitterHandle: twitterHandle,
|
||||
#instagramHandle: instagramHandle,
|
||||
#tiktokHandle: tiktokHandle,
|
||||
#websiteUrl: websiteUrl,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<_i4.User>.value(_FakeUser_2(
|
||||
this,
|
||||
Invocation.method(
|
||||
#updateProfile,
|
||||
[],
|
||||
{
|
||||
#userId: userId,
|
||||
#username: username,
|
||||
#avatarUrl: avatarUrl,
|
||||
#bio: bio,
|
||||
#isPublicProfile: isPublicProfile,
|
||||
#twitterHandle: twitterHandle,
|
||||
#instagramHandle: instagramHandle,
|
||||
#tiktokHandle: tiktokHandle,
|
||||
#websiteUrl: websiteUrl,
|
||||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i4.User>);
|
||||
|
||||
@override
|
||||
_i7.Future<bool> isUsernameAvailable(String? username) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#isUsernameAvailable,
|
||||
[username],
|
||||
),
|
||||
returnValue: _i7.Future<bool>.value(false),
|
||||
) as _i7.Future<bool>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> deleteAccount(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#deleteAccount,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [SocialRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockSocialRepository extends _i1.Mock implements _i12.SocialRepository {
|
||||
MockSocialRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i7.Future<void> followUser(
|
||||
String? userId,
|
||||
String? targetUserId,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#followUser,
|
||||
[
|
||||
userId,
|
||||
targetUserId,
|
||||
],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> unfollowUser(
|
||||
String? userId,
|
||||
String? targetUserId,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unfollowUser,
|
||||
[
|
||||
userId,
|
||||
targetUserId,
|
||||
],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<bool> isFollowing(
|
||||
String? userId,
|
||||
String? targetUserId,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#isFollowing,
|
||||
[
|
||||
userId,
|
||||
targetUserId,
|
||||
],
|
||||
),
|
||||
returnValue: _i7.Future<bool>.value(false),
|
||||
) as _i7.Future<bool>);
|
||||
|
||||
@override
|
||||
_i7.Future<List<_i4.User>> getFollowers(String? userId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getFollowers,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<List<_i4.User>>.value(<_i4.User>[]),
|
||||
) as _i7.Future<List<_i4.User>>);
|
||||
|
||||
@override
|
||||
_i7.Future<List<_i4.User>> getFollowing(String? userId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getFollowing,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<List<_i4.User>>.value(<_i4.User>[]),
|
||||
) as _i7.Future<List<_i4.User>>);
|
||||
|
||||
@override
|
||||
_i7.Future<List<_i5.Activity>> getActivityFeed(String? userId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getActivityFeed,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<List<_i5.Activity>>.value(<_i5.Activity>[]),
|
||||
) as _i7.Future<List<_i5.Activity>>);
|
||||
|
||||
@override
|
||||
_i7.Future<_i5.Activity> logActivity({
|
||||
required String? userId,
|
||||
required String? type,
|
||||
Map<String, dynamic>? payload,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#logActivity,
|
||||
[],
|
||||
{
|
||||
#userId: userId,
|
||||
#type: type,
|
||||
#payload: payload,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<_i5.Activity>.value(_FakeActivity_3(
|
||||
this,
|
||||
Invocation.method(
|
||||
#logActivity,
|
||||
[],
|
||||
{
|
||||
#userId: userId,
|
||||
#type: type,
|
||||
#payload: payload,
|
||||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i5.Activity>);
|
||||
|
||||
@override
|
||||
_i7.Future<List<_i4.User>> getLeaderboard({
|
||||
required String? sortBy,
|
||||
int? limit = 50,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getLeaderboard,
|
||||
[],
|
||||
{
|
||||
#sortBy: sortBy,
|
||||
#limit: limit,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<List<_i4.User>>.value(<_i4.User>[]),
|
||||
) as _i7.Future<List<_i4.User>>);
|
||||
}
|
||||
|
||||
/// A class which mocks [NotificationsRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockNotificationsRepository extends _i1.Mock
|
||||
implements _i13.NotificationsRepository {
|
||||
MockNotificationsRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i7.Future<void> showNotification({
|
||||
required int? id,
|
||||
required String? title,
|
||||
required String? body,
|
||||
String? payload,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#showNotification,
|
||||
[],
|
||||
{
|
||||
#id: id,
|
||||
#title: title,
|
||||
#body: body,
|
||||
#payload: payload,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> scheduleNotification({
|
||||
required int? id,
|
||||
required String? title,
|
||||
required String? body,
|
||||
required DateTime? scheduledDate,
|
||||
String? payload,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#scheduleNotification,
|
||||
[],
|
||||
{
|
||||
#id: id,
|
||||
#title: title,
|
||||
#body: body,
|
||||
#scheduledDate: scheduledDate,
|
||||
#payload: payload,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> scheduleDailyReminder({
|
||||
required int? id,
|
||||
required String? title,
|
||||
required String? body,
|
||||
required int? hour,
|
||||
required int? minute,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#scheduleDailyReminder,
|
||||
[],
|
||||
{
|
||||
#id: id,
|
||||
#title: title,
|
||||
#body: body,
|
||||
#hour: hour,
|
||||
#minute: minute,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> cancelNotification(int? id) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#cancelNotification,
|
||||
[id],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<void> cancelAllNotifications() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#cancelAllNotifications,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
|
||||
@override
|
||||
_i7.Future<List<_i14.PendingNotificationRequest>> getPendingNotifications() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getPendingNotifications,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<List<_i14.PendingNotificationRequest>>.value(
|
||||
<_i14.PendingNotificationRequest>[]),
|
||||
) as _i7.Future<List<_i14.PendingNotificationRequest>>);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import 'package:lifetimer/data/models/user_model.dart';
|
||||
import 'package:lifetimer/data/models/goal_model.dart';
|
||||
import 'package:lifetimer/data/models/goal_step_model.dart';
|
||||
import 'package:lifetimer/data/models/activity_model.dart';
|
||||
|
||||
/// Helper class to create test data
|
||||
class TestData {
|
||||
/// Create a test user
|
||||
static User createTestUser({
|
||||
String id = 'test-user-id',
|
||||
String username = 'testuser',
|
||||
String email = 'test@example.com',
|
||||
String? avatarUrl,
|
||||
String? bio,
|
||||
bool isPublicProfile = false,
|
||||
DateTime? countdownStartDate,
|
||||
DateTime? countdownEndDate,
|
||||
}) {
|
||||
return User(
|
||||
id: id,
|
||||
username: username,
|
||||
email: email,
|
||||
avatarUrl: avatarUrl,
|
||||
bio: bio,
|
||||
isPublicProfile: isPublicProfile,
|
||||
countdownStartDate: countdownStartDate,
|
||||
countdownEndDate: countdownEndDate,
|
||||
createdAt: DateTime.now().subtract(const Duration(days: 30)),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a test goal
|
||||
static Goal createTestGoal({
|
||||
String id = 'test-goal-id',
|
||||
String ownerId = 'test-user-id',
|
||||
String title = 'Test Goal',
|
||||
String? description,
|
||||
int progress = 0,
|
||||
double? locationLat,
|
||||
double? locationLng,
|
||||
String? locationName,
|
||||
String? imageUrl,
|
||||
bool completed = false,
|
||||
}) {
|
||||
return Goal(
|
||||
id: id,
|
||||
ownerId: ownerId,
|
||||
title: title,
|
||||
description: description,
|
||||
progress: progress,
|
||||
locationLat: locationLat,
|
||||
locationLng: locationLng,
|
||||
locationName: locationName,
|
||||
imageUrl: imageUrl,
|
||||
completed: completed,
|
||||
createdAt: DateTime.now().subtract(const Duration(days: 10)),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a test goal step
|
||||
static GoalStep createTestGoalStep({
|
||||
String id = 'test-step-id',
|
||||
String goalId = 'test-goal-id',
|
||||
String title = 'Test Step',
|
||||
bool isDone = false,
|
||||
int orderIndex = 0,
|
||||
}) {
|
||||
return GoalStep(
|
||||
id: id,
|
||||
goalId: goalId,
|
||||
title: title,
|
||||
isDone: isDone,
|
||||
orderIndex: orderIndex,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a test activity
|
||||
static Activity createTestActivity({
|
||||
String id = 'test-activity-id',
|
||||
String userId = 'test-user-id',
|
||||
String type = 'goal_created',
|
||||
Map<String, dynamic>? payload,
|
||||
}) {
|
||||
return Activity(
|
||||
id: id,
|
||||
userId: userId,
|
||||
type: type,
|
||||
payload: payload,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a list of test goals
|
||||
static List<Goal> createTestGoalsList({int count = 5}) {
|
||||
return List.generate(
|
||||
count,
|
||||
(index) => createTestGoal(
|
||||
id: 'goal-$index',
|
||||
title: 'Test Goal $index',
|
||||
progress: index * 20,
|
||||
completed: index == count - 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a list of test goal steps
|
||||
static List<GoalStep> createTestStepsList({
|
||||
required String goalId,
|
||||
int count = 3,
|
||||
}) {
|
||||
return List.generate(
|
||||
count,
|
||||
(index) => createTestGoalStep(
|
||||
id: 'step-$index',
|
||||
goalId: goalId,
|
||||
title: 'Step $index',
|
||||
isDone: index < count ~/ 2,
|
||||
orderIndex: index,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a list of test activities
|
||||
static List<Activity> createTestActivitiesList({int count = 5}) {
|
||||
final types = ['goal_created', 'goal_completed', 'countdown_started'];
|
||||
return List.generate(
|
||||
count,
|
||||
(index) => createTestActivity(
|
||||
id: 'activity-$index',
|
||||
type: types[index % types.length],
|
||||
payload: {'goal_id': 'goal-$index'},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// Helper to create a ProviderScope with mocked providers for testing
|
||||
ProviderScope createTestProviderScope({
|
||||
required Widget child,
|
||||
List<Override> overrides = const [],
|
||||
}) {
|
||||
return ProviderScope(
|
||||
overrides: overrides,
|
||||
child: MaterialApp(
|
||||
home: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper to pump and settle a widget with ProviderScope
|
||||
Future<void> pumpTestWidget(
|
||||
WidgetTester tester,
|
||||
Widget child, {
|
||||
List<Override> overrides = const [],
|
||||
}) async {
|
||||
await tester.pumpWidget(
|
||||
createTestProviderScope(
|
||||
child: child,
|
||||
overrides: overrides,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Helper to find a widget by type and key
|
||||
Finder findWidgetByKey<T extends Widget>(Key key) {
|
||||
return find.byWidgetPredicate((widget) =>
|
||||
widget is T && widget.key == key);
|
||||
}
|
||||
|
||||
/// Helper to verify a widget exists and has specific text
|
||||
void expectWidgetWithText<T extends Widget>(String text) {
|
||||
expect(find.text(text), findsOneWidget);
|
||||
}
|
||||
|
||||
/// Helper to verify a widget doesn't exist
|
||||
void expectNoWidgetWithText<T extends Widget>(String text) {
|
||||
expect(find.text(text), findsNothing);
|
||||
}
|
||||
|
||||
/// Helper to tap a widget with specific text
|
||||
Future<void> tapWidgetWithText(WidgetTester tester, String text) async {
|
||||
await tester.tap(find.text(text));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Helper to tap a widget by type
|
||||
Future<void> tapWidgetByType<T extends Widget>(WidgetTester tester) async {
|
||||
await tester.tap(find.byType(T));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Helper to enter text in a text field
|
||||
Future<void> enterTextInField(
|
||||
WidgetTester tester,
|
||||
Finder finder,
|
||||
String text,
|
||||
) async {
|
||||
await tester.enterText(finder, text);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Helper to scroll until a widget is found
|
||||
Future<void> scrollUntilVisible(
|
||||
WidgetTester tester,
|
||||
Finder finder, {
|
||||
Finder? scrollable,
|
||||
}) async {
|
||||
final scrollableFinder = scrollable ?? find.byType(Scrollable);
|
||||
await tester.scrollUntilVisible(
|
||||
finder,
|
||||
500.0,
|
||||
scrollable: scrollableFinder,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Helper to wait for a specific duration
|
||||
Future<void> waitFor(Duration duration) async {
|
||||
await Future.delayed(duration);
|
||||
}
|
||||
@@ -5,26 +5,24 @@
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:lifetimer/bootstrap/bootstrap.dart';
|
||||
import 'package:lifetimer/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
testWidgets('LifeTimerApp builds without crashing', (WidgetTester tester) async {
|
||||
// Ensure app services (e.g., Supabase) are initialized similar to production.
|
||||
await bootstrap();
|
||||
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(const ProviderScope(
|
||||
child: LifeTimerApp(),
|
||||
));
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
// Pump a few frames to allow initial build/layout without waiting for
|
||||
// all animations/streams to settle indefinitely.
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user