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,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)));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user