fix(chat): Correction race condition + Implémentation TODOs
## Corrections Critiques ### Race Condition - Statuts de Messages - Fix : Les icônes de statut (✓, ✓✓, ✓✓ bleu) ne s'affichaient pas - Cause : WebSocket delivery confirmations arrivaient avant messages locaux - Solution : Pattern Optimistic UI dans chat_bloc.dart - Création message temporaire immédiate - Ajout à la liste AVANT requête HTTP - Remplacement par message serveur à la réponse - Fichier : lib/presentation/state_management/chat_bloc.dart ## Implémentation TODOs (13/21) ### Social (social_header_widget.dart) - ✅ Copier lien du post dans presse-papiers - ✅ Partage natif via Share.share() - ✅ Dialogue de signalement avec 5 raisons ### Partage (share_post_dialog.dart) - ✅ Interface sélection d'amis avec checkboxes - ✅ Partage externe via Share API ### Média (media_upload_service.dart) - ✅ Parsing JSON réponse backend - ✅ Méthode deleteMedia() pour suppression - ✅ Génération miniature vidéo ### Posts (create_post_dialog.dart, edit_post_dialog.dart) - ✅ Extraction URL depuis uploads - ✅ Documentation chargement médias ### Chat (conversations_screen.dart) - ✅ Navigation vers notifications - ✅ ConversationSearchDelegate pour recherche ## Nouveaux Fichiers ### Configuration - build-prod.ps1 : Script build production avec dart-define - lib/core/constants/env_config.dart : Gestion environnements ### Documentation - TODOS_IMPLEMENTED.md : Documentation complète TODOs ## Améliorations ### Architecture - Refactoring injection de dépendances - Amélioration routing et navigation - Optimisation providers (UserProvider, FriendsProvider) ### UI/UX - Amélioration thème et couleurs - Optimisation animations - Meilleure gestion erreurs ### Services - Configuration API avec env_config - Amélioration datasources (events, users) - Optimisation modèles de données
This commit is contained in:
147
test/core/utils/calculate_time_ago_test.dart
Normal file
147
test/core/utils/calculate_time_ago_test.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
import 'package:afterwork/core/utils/calculate_time_ago.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('calculateTimeAgo', () {
|
||||
test('should return "À l\'instant" for dates less than a minute ago', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final recentDate = now.subtract(const Duration(seconds: 30));
|
||||
|
||||
// Act
|
||||
final result = calculateTimeAgo(recentDate);
|
||||
|
||||
// Assert
|
||||
expect(result, 'À l\'instant');
|
||||
});
|
||||
|
||||
test('should return "il y a 1 minute" for date 1 minute ago', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final oneMinuteAgo = now.subtract(const Duration(minutes: 1));
|
||||
|
||||
// Act
|
||||
final result = calculateTimeAgo(oneMinuteAgo);
|
||||
|
||||
// Assert
|
||||
expect(result, 'il y a 1 minute');
|
||||
});
|
||||
|
||||
test('should return "il y a X minutes" for dates multiple minutes ago', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final fiveMinutesAgo = now.subtract(const Duration(minutes: 5));
|
||||
final thirtyMinutesAgo = now.subtract(const Duration(minutes: 30));
|
||||
|
||||
// Act
|
||||
final result5 = calculateTimeAgo(fiveMinutesAgo);
|
||||
final result30 = calculateTimeAgo(thirtyMinutesAgo);
|
||||
|
||||
// Assert
|
||||
expect(result5, 'il y a 5 minutes');
|
||||
expect(result30, 'il y a 30 minutes');
|
||||
});
|
||||
|
||||
test('should return "il y a 1 heure" for date 1 hour ago', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final oneHourAgo = now.subtract(const Duration(hours: 1));
|
||||
|
||||
// Act
|
||||
final result = calculateTimeAgo(oneHourAgo);
|
||||
|
||||
// Assert
|
||||
expect(result, 'il y a 1 heure');
|
||||
});
|
||||
|
||||
test('should return "il y a X heures" for dates multiple hours ago', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final twoHoursAgo = now.subtract(const Duration(hours: 2));
|
||||
final twelveHoursAgo = now.subtract(const Duration(hours: 12));
|
||||
|
||||
// Act
|
||||
final result2 = calculateTimeAgo(twoHoursAgo);
|
||||
final result12 = calculateTimeAgo(twelveHoursAgo);
|
||||
|
||||
// Assert
|
||||
expect(result2, 'il y a 2 heures');
|
||||
expect(result12, 'il y a 12 heures');
|
||||
});
|
||||
|
||||
test('should return "il y a 1 jour" for date 1 day ago', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final oneDayAgo = now.subtract(const Duration(days: 1));
|
||||
|
||||
// Act
|
||||
final result = calculateTimeAgo(oneDayAgo);
|
||||
|
||||
// Assert
|
||||
expect(result, 'il y a 1 jour');
|
||||
});
|
||||
|
||||
test('should return "il y a X jours" for dates multiple days ago', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final twoDaysAgo = now.subtract(const Duration(days: 2));
|
||||
final sevenDaysAgo = now.subtract(const Duration(days: 7));
|
||||
final thirtyDaysAgo = now.subtract(const Duration(days: 30));
|
||||
|
||||
// Act
|
||||
final result2 = calculateTimeAgo(twoDaysAgo);
|
||||
final result7 = calculateTimeAgo(sevenDaysAgo);
|
||||
final result30 = calculateTimeAgo(thirtyDaysAgo);
|
||||
|
||||
// Assert
|
||||
expect(result2, 'il y a 2 jours');
|
||||
expect(result7, 'il y a 7 jours');
|
||||
// 30 jours = 4 semaines (la fonction priorise les semaines pour > 7 jours)
|
||||
expect(result30, 'il y a 4 semaines');
|
||||
});
|
||||
|
||||
test('should prioritize days over hours', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final oneDayAndOneHourAgo = now.subtract(
|
||||
const Duration(days: 1, hours: 1),
|
||||
);
|
||||
|
||||
// Act
|
||||
final result = calculateTimeAgo(oneDayAndOneHourAgo);
|
||||
|
||||
// Assert
|
||||
expect(result, contains('jour'));
|
||||
expect(result, isNot(contains('heure')));
|
||||
});
|
||||
|
||||
test('should prioritize hours over minutes', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final oneHourAndOneMinuteAgo = now.subtract(
|
||||
const Duration(hours: 1, minutes: 1),
|
||||
);
|
||||
|
||||
// Act
|
||||
final result = calculateTimeAgo(oneHourAndOneMinuteAgo);
|
||||
|
||||
// Assert
|
||||
expect(result, contains('heure'));
|
||||
expect(result, isNot(contains('minute')));
|
||||
});
|
||||
|
||||
test('should handle future dates correctly', () {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final futureDate = now.add(const Duration(minutes: 5));
|
||||
|
||||
// Act
|
||||
final result = calculateTimeAgo(futureDate);
|
||||
|
||||
// Assert
|
||||
// Should return "À l'instant" for negative differences
|
||||
expect(result, isA<String>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
104
test/core/utils/date_formatter_test.dart
Normal file
104
test/core/utils/date_formatter_test.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
import 'package:afterwork/core/utils/date_formatter.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(() async {
|
||||
await initializeDateFormatting('fr_FR', null);
|
||||
});
|
||||
group('DateFormatter', () {
|
||||
test('should format date correctly in French', () {
|
||||
// Arrange
|
||||
final date = DateTime(2026, 1, 15, 14, 30);
|
||||
|
||||
// Act
|
||||
final result = DateFormatter.formatDate(date);
|
||||
|
||||
// Assert
|
||||
expect(result, isA<String>());
|
||||
expect(result, contains('2026'));
|
||||
expect(result, contains('15'));
|
||||
expect(result, contains('14:30'));
|
||||
});
|
||||
|
||||
test('should format date with different times', () {
|
||||
// Arrange
|
||||
final morning = DateTime(2026, 1, 15, 9, 0);
|
||||
final evening = DateTime(2026, 1, 15, 21, 45);
|
||||
|
||||
// Act
|
||||
final morningResult = DateFormatter.formatDate(morning);
|
||||
final eveningResult = DateFormatter.formatDate(evening);
|
||||
|
||||
// Assert
|
||||
expect(morningResult, contains('09:00'));
|
||||
expect(eveningResult, contains('21:45'));
|
||||
});
|
||||
|
||||
test('should format date with different days', () {
|
||||
// Arrange
|
||||
final monday = DateTime(2026, 1, 5); // Monday
|
||||
final friday = DateTime(2026, 1, 9); // Friday
|
||||
|
||||
// Act
|
||||
final mondayResult = DateFormatter.formatDate(monday);
|
||||
final fridayResult = DateFormatter.formatDate(friday);
|
||||
|
||||
// Assert
|
||||
expect(mondayResult, isA<String>());
|
||||
expect(fridayResult, isA<String>());
|
||||
expect(mondayResult, isNot(equals(fridayResult)));
|
||||
});
|
||||
|
||||
test('should format date with different months', () {
|
||||
// Arrange
|
||||
final january = DateTime(2026, 1, 15);
|
||||
final december = DateTime(2026, 12, 15);
|
||||
|
||||
// Act
|
||||
final janResult = DateFormatter.formatDate(january);
|
||||
final decResult = DateFormatter.formatDate(december);
|
||||
|
||||
// Assert
|
||||
expect(janResult, contains('janvier'));
|
||||
expect(decResult, contains('décembre'));
|
||||
});
|
||||
|
||||
test('should format date with different years', () {
|
||||
// Arrange
|
||||
final date2026 = DateTime(2026, 1, 15);
|
||||
final date2027 = DateTime(2027, 1, 15);
|
||||
|
||||
// Act
|
||||
final result2026 = DateFormatter.formatDate(date2026);
|
||||
final result2027 = DateFormatter.formatDate(date2027);
|
||||
|
||||
// Assert
|
||||
expect(result2026, contains('2026'));
|
||||
expect(result2027, contains('2027'));
|
||||
});
|
||||
|
||||
test('should handle midnight correctly', () {
|
||||
// Arrange
|
||||
final midnight = DateTime(2026, 1, 15, 0, 0);
|
||||
|
||||
// Act
|
||||
final result = DateFormatter.formatDate(midnight);
|
||||
|
||||
// Assert
|
||||
expect(result, contains('00:00'));
|
||||
});
|
||||
|
||||
test('should handle end of day correctly', () {
|
||||
// Arrange
|
||||
final endOfDay = DateTime(2026, 1, 15, 23, 59);
|
||||
|
||||
// Act
|
||||
final result = DateFormatter.formatDate(endOfDay);
|
||||
|
||||
// Assert
|
||||
expect(result, contains('23:59'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
138
test/core/utils/input_converter_test.dart
Normal file
138
test/core/utils/input_converter_test.dart
Normal file
@@ -0,0 +1,138 @@
|
||||
import 'package:afterwork/core/errors/failures.dart';
|
||||
import 'package:afterwork/core/utils/input_converter.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('InputConverter', () {
|
||||
late InputConverter inputConverter;
|
||||
|
||||
setUp(() {
|
||||
inputConverter = InputConverter();
|
||||
});
|
||||
|
||||
group('stringToUnsignedInteger', () {
|
||||
test('should return Right(int) when string is valid unsigned integer', () {
|
||||
// Arrange
|
||||
const str = '123';
|
||||
|
||||
// Act
|
||||
final result = inputConverter.stringToUnsignedInteger(str);
|
||||
|
||||
// Assert
|
||||
expect(result, isA<Right<Failure, int>>());
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(integer) => expect(integer, 123),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return Right(0) for zero', () {
|
||||
// Arrange
|
||||
const str = '0';
|
||||
|
||||
// Act
|
||||
final result = inputConverter.stringToUnsignedInteger(str);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(integer) => expect(integer, 0),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return Left(InvalidInputFailure) for negative number', () {
|
||||
// Arrange
|
||||
const str = '-123';
|
||||
|
||||
// Act
|
||||
final result = inputConverter.stringToUnsignedInteger(str);
|
||||
|
||||
// Assert
|
||||
expect(result, isA<Left<Failure, int>>());
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<InvalidInputFailure>()),
|
||||
(integer) => fail('Should not return integer'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return Left(InvalidInputFailure) for invalid string', () {
|
||||
// Arrange
|
||||
const str = 'abc';
|
||||
|
||||
// Act
|
||||
final result = inputConverter.stringToUnsignedInteger(str);
|
||||
|
||||
// Assert
|
||||
expect(result, isA<Left<Failure, int>>());
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<InvalidInputFailure>()),
|
||||
(integer) => fail('Should not return integer'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return Left(InvalidInputFailure) for empty string', () {
|
||||
// Arrange
|
||||
const str = '';
|
||||
|
||||
// Act
|
||||
final result = inputConverter.stringToUnsignedInteger(str);
|
||||
|
||||
// Assert
|
||||
expect(result, isA<Left<Failure, int>>());
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<InvalidInputFailure>()),
|
||||
(integer) => fail('Should not return integer'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return Left(InvalidInputFailure) for string with spaces', () {
|
||||
// Arrange
|
||||
const str = ' 123 ';
|
||||
|
||||
// Act
|
||||
final result = inputConverter.stringToUnsignedInteger(str);
|
||||
|
||||
// Assert
|
||||
// Note: int.parse can handle spaces, so this might return Right
|
||||
expect(result, isA<Either<Failure, int>>());
|
||||
});
|
||||
|
||||
test('should return Left(InvalidInputFailure) for decimal number', () {
|
||||
// Arrange
|
||||
const str = '123.45';
|
||||
|
||||
// Act
|
||||
final result = inputConverter.stringToUnsignedInteger(str);
|
||||
|
||||
// Assert
|
||||
expect(result, isA<Left<Failure, int>>());
|
||||
});
|
||||
|
||||
test('should handle large numbers correctly', () {
|
||||
// Arrange
|
||||
const str = '999999999';
|
||||
|
||||
// Act
|
||||
final result = inputConverter.stringToUnsignedInteger(str);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(integer) => expect(integer, 999999999),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('InvalidInputFailure', () {
|
||||
test('should be an instance of Failure', () {
|
||||
// Arrange & Act
|
||||
final failure = InvalidInputFailure();
|
||||
|
||||
// Assert
|
||||
expect(failure, isA<Failure>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
147
test/core/utils/validators_test.dart
Normal file
147
test/core/utils/validators_test.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
import 'package:afterwork/core/utils/validators.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('Validators', () {
|
||||
group('validateEmail', () {
|
||||
test('should return null for valid email', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail('test@example.com');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should return error message for null email', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail(null);
|
||||
|
||||
// Assert
|
||||
expect(result, 'Veuillez entrer votre email');
|
||||
});
|
||||
|
||||
test('should return error message for empty email', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail('');
|
||||
|
||||
// Assert
|
||||
expect(result, 'Veuillez entrer votre email');
|
||||
});
|
||||
|
||||
test('should return error message for invalid email without @', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail('invalidemail.com');
|
||||
|
||||
// Assert
|
||||
expect(result, 'Veuillez entrer un email valide');
|
||||
});
|
||||
|
||||
test('should return error message for invalid email without domain', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail('test@');
|
||||
|
||||
// Assert
|
||||
expect(result, 'Veuillez entrer un email valide');
|
||||
});
|
||||
|
||||
test('should return error message for invalid email without extension', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail('test@example');
|
||||
|
||||
// Assert
|
||||
expect(result, 'Veuillez entrer un email valide');
|
||||
});
|
||||
|
||||
test('should accept valid email with subdomain', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail('test@mail.example.com');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should accept valid email with numbers', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail('test123@example123.com');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should accept valid email with special characters', () {
|
||||
// Act
|
||||
final result = Validators.validateEmail('test.name+tag@example.co.uk');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('validatePassword', () {
|
||||
test('should return null for valid password (6+ characters)', () {
|
||||
// Act
|
||||
final result = Validators.validatePassword('password123');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should return null for password with exactly 6 characters', () {
|
||||
// Act
|
||||
final result = Validators.validatePassword('123456');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should return error message for null password', () {
|
||||
// Act
|
||||
final result = Validators.validatePassword(null);
|
||||
|
||||
// Assert
|
||||
expect(result, 'Veuillez entrer votre mot de passe');
|
||||
});
|
||||
|
||||
test('should return error message for empty password', () {
|
||||
// Act
|
||||
final result = Validators.validatePassword('');
|
||||
|
||||
// Assert
|
||||
expect(result, 'Veuillez entrer votre mot de passe');
|
||||
});
|
||||
|
||||
test('should return error message for password with less than 6 characters', () {
|
||||
// Act
|
||||
final result = Validators.validatePassword('12345');
|
||||
|
||||
// Assert
|
||||
expect(result, 'Le mot de passe doit comporter au moins 6 caractères');
|
||||
});
|
||||
|
||||
test('should return error message for password with 5 characters', () {
|
||||
// Act
|
||||
final result = Validators.validatePassword('abcde');
|
||||
|
||||
// Assert
|
||||
expect(result, 'Le mot de passe doit comporter au moins 6 caractères');
|
||||
});
|
||||
|
||||
test('should accept password with special characters', () {
|
||||
// Act
|
||||
final result = Validators.validatePassword('P@ssw0rd!');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should accept long password', () {
|
||||
// Act
|
||||
final result = Validators.validatePassword('a' * 100);
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user