Models (Task, Streak, StreakEntry, Reward, User, TimeEstimate, CoworkingRoom, ApiResponse), enums (EnergyLevel, TaskStatus, RewardType, RewardStyle), constants (ApiConstants, ErrorCodes, AppLimits), validators, and generated .g.dart files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
206 lines
6.4 KiB
Dart
206 lines
6.4 KiB
Dart
import 'package:focusflow_shared/focusflow_shared.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('Task', () {
|
|
test('creates with required fields', () {
|
|
final task = Task(
|
|
id: 'task-1',
|
|
userId: 'user-1',
|
|
title: 'Test task',
|
|
createdAt: DateTime(2024, 1, 1),
|
|
);
|
|
expect(task.id, 'task-1');
|
|
expect(task.title, 'Test task');
|
|
expect(task.status, 'pending');
|
|
expect(task.priority, 0);
|
|
expect(task.energyLevel, 'medium');
|
|
expect(task.noveltyFactor, 1.0);
|
|
expect(task.timesPostponed, 0);
|
|
expect(task.tags, isEmpty);
|
|
expect(task.version, 1);
|
|
});
|
|
|
|
test('serializes to JSON and back', () {
|
|
final task = Task(
|
|
id: 'task-1',
|
|
userId: 'user-1',
|
|
title: 'Test task',
|
|
priority: 50,
|
|
tags: ['focus', 'work'],
|
|
createdAt: DateTime(2024, 1, 1),
|
|
);
|
|
final json = task.toJson();
|
|
final restored = Task.fromJson(json);
|
|
expect(restored.id, task.id);
|
|
expect(restored.title, task.title);
|
|
expect(restored.priority, task.priority);
|
|
expect(restored.tags, task.tags);
|
|
});
|
|
});
|
|
|
|
group('Streak', () {
|
|
test('creates with defaults', () {
|
|
final streak = Streak(
|
|
id: 's-1',
|
|
userId: 'user-1',
|
|
name: 'Morning routine',
|
|
createdAt: DateTime(2024, 1, 1),
|
|
);
|
|
expect(streak.streakType, 'daily');
|
|
expect(streak.currentCount, 0);
|
|
expect(streak.graceDays, 2);
|
|
expect(streak.isActive, true);
|
|
expect(streak.encouragementShown, false);
|
|
});
|
|
|
|
test('serializes to JSON and back', () {
|
|
final streak = Streak(
|
|
id: 's-1',
|
|
userId: 'user-1',
|
|
name: 'Morning routine',
|
|
currentCount: 7,
|
|
longestCount: 14,
|
|
createdAt: DateTime(2024, 1, 1),
|
|
);
|
|
final json = streak.toJson();
|
|
final restored = Streak.fromJson(json);
|
|
expect(restored.name, streak.name);
|
|
expect(restored.currentCount, 7);
|
|
expect(restored.longestCount, 14);
|
|
});
|
|
});
|
|
|
|
group('User', () {
|
|
test('creates with defaults', () {
|
|
final user = User(
|
|
id: 'u-1',
|
|
email: 'test@example.com',
|
|
displayName: 'Test User',
|
|
createdAt: DateTime(2024, 1, 1),
|
|
);
|
|
expect(user.timezone, 'UTC');
|
|
expect(user.onboardingCompleted, false);
|
|
expect(user.preferredTaskLoad, 5);
|
|
expect(user.focusSessionMinutes, 25);
|
|
expect(user.rewardStyle, 'playful');
|
|
expect(user.forgivenessEnabled, true);
|
|
expect(user.subscriptionTier, 'free');
|
|
});
|
|
});
|
|
|
|
group('ApiResponse', () {
|
|
test('creates success response', () {
|
|
final response = ApiResponse<String>.success('hello');
|
|
expect(response.status, 'success');
|
|
expect(response.data, 'hello');
|
|
expect(response.error, isNull);
|
|
});
|
|
|
|
test('creates error response', () {
|
|
final response = ApiResponse<String>.error('NOT_FOUND', 'Task not found');
|
|
expect(response.status, 'error');
|
|
expect(response.data, isNull);
|
|
expect(response.error!.code, 'NOT_FOUND');
|
|
expect(response.error!.message, 'Task not found');
|
|
});
|
|
});
|
|
|
|
group('TaskValidator', () {
|
|
test('validates title - empty', () {
|
|
expect(TaskValidator.validateTitle(null), 'Title is required');
|
|
expect(TaskValidator.validateTitle(''), 'Title is required');
|
|
expect(TaskValidator.validateTitle(' '), 'Title is required');
|
|
});
|
|
|
|
test('validates title - too long', () {
|
|
final longTitle = 'x' * 501;
|
|
expect(TaskValidator.validateTitle(longTitle), 'Title must be 500 characters or less');
|
|
});
|
|
|
|
test('validates title - valid', () {
|
|
expect(TaskValidator.validateTitle('Buy groceries'), isNull);
|
|
});
|
|
|
|
test('validates estimated minutes', () {
|
|
expect(TaskValidator.validateEstimatedMinutes(null), isNull);
|
|
expect(TaskValidator.validateEstimatedMinutes(0), 'Estimate must be at least 1 minute');
|
|
expect(TaskValidator.validateEstimatedMinutes(481), 'Estimate cannot exceed 8 hours');
|
|
expect(TaskValidator.validateEstimatedMinutes(30), isNull);
|
|
});
|
|
|
|
test('validates priority', () {
|
|
expect(TaskValidator.validatePriority(null), isNull);
|
|
expect(TaskValidator.validatePriority(-1), 'Priority must be 0-100');
|
|
expect(TaskValidator.validatePriority(101), 'Priority must be 0-100');
|
|
expect(TaskValidator.validatePriority(50), isNull);
|
|
});
|
|
});
|
|
|
|
group('Enums', () {
|
|
test('TaskStatus display names', () {
|
|
expect(TaskStatus.pending.displayName, 'To Do');
|
|
expect(TaskStatus.inProgress.displayName, 'In Progress');
|
|
expect(TaskStatus.completed.displayName, 'Done');
|
|
});
|
|
|
|
test('TaskStatus API values', () {
|
|
expect(TaskStatus.pending.apiValue, 'pending');
|
|
expect(TaskStatus.inProgress.apiValue, 'in_progress');
|
|
});
|
|
|
|
test('EnergyLevel display names', () {
|
|
expect(EnergyLevel.low.displayName, 'Low Energy');
|
|
expect(EnergyLevel.medium.displayName, 'Medium Energy');
|
|
expect(EnergyLevel.high.displayName, 'High Energy');
|
|
});
|
|
|
|
test('RewardType display names', () {
|
|
expect(RewardType.animation.displayName, 'Celebration');
|
|
expect(RewardType.surprise.displayName, 'Surprise!');
|
|
});
|
|
|
|
test('RewardStyle display names', () {
|
|
expect(RewardStyle.playful.displayName, 'Playful (animations & messages)');
|
|
expect(RewardStyle.data.displayName, 'Data-driven (stats & charts)');
|
|
});
|
|
});
|
|
|
|
group('ApiConstants', () {
|
|
test('base path', () {
|
|
expect(ApiConstants.basePath, '/api/v1');
|
|
});
|
|
|
|
test('task routes', () {
|
|
expect(ApiConstants.tasks, '/api/v1/tasks');
|
|
expect(ApiConstants.task('abc'), '/api/v1/tasks/abc');
|
|
expect(ApiConstants.completeTask('abc'), '/api/v1/tasks/abc/complete');
|
|
});
|
|
|
|
test('room routes', () {
|
|
expect(ApiConstants.rooms, '/api/v1/rooms');
|
|
expect(ApiConstants.joinRoom('r1'), '/api/v1/rooms/r1/join');
|
|
});
|
|
});
|
|
|
|
group('AppLimits', () {
|
|
test('free tier limits', () {
|
|
expect(AppLimits.freeMaxActiveTasks, 15);
|
|
expect(AppLimits.freeMaxStreaks, 3);
|
|
});
|
|
|
|
test('pagination defaults', () {
|
|
expect(AppLimits.defaultPageSize, 20);
|
|
expect(AppLimits.maxPageSize, 100);
|
|
});
|
|
});
|
|
|
|
group('ErrorCodes', () {
|
|
test('error code values', () {
|
|
expect(ErrorCodes.taskNotFound, 'TASK_NOT_FOUND');
|
|
expect(ErrorCodes.premiumRequired, 'PREMIUM_REQUIRED');
|
|
expect(ErrorCodes.rateLimited, 'RATE_LIMITED');
|
|
});
|
|
});
|
|
}
|