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:
dahoud
2026-01-10 10:43:17 +00:00
parent 06031b01f2
commit 92612abbd7
321 changed files with 43137 additions and 4285 deletions

View 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>());
});
});
}

View 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'));
});
});
}

View 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>());
});
});
}

View 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);
});
});
});
}