Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2216d5d
explore foundation stone
mohamed-yasser23 Nov 19, 2025
040c00b
UI foundation
mohamed-yasser23 Nov 21, 2025
bcb0b65
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Nov 27, 2025
33e2b2d
save for a merge
mohamed-yasser23 Nov 28, 2025
88138ae
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Nov 28, 2025
7484f1a
pre ui refining
mohamed-yasser23 Dec 4, 2025
4c5c84e
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 4, 2025
8af4c90
merged with dev
mohamed-yasser23 Dec 4, 2025
fffda0e
search finished
mohamed-yasser23 Dec 5, 2025
38025be
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 6, 2025
b96881b
ai summary done , fixed some errors
mohamed-yasser23 Dec 6, 2025
73526ea
Merge branch 'Feature/explore-search' of https://github.com/SWEProjec…
mohamed-yasser23 Dec 6, 2025
702a69c
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 6, 2025
10dd56f
fixed a bug with latest search
mohamed-yasser23 Dec 6, 2025
e52953f
finished ?
mohamed-yasser23 Dec 10, 2025
9320230
little provider removed
mohamed-yasser23 Dec 10, 2025
b9aa4da
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 10, 2025
0a7625e
h
mohamed-yasser23 Dec 10, 2025
33122b7
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 11, 2025
1527f0a
fixed bugs
mohamed-yasser23 Dec 11, 2025
c3423fd
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 11, 2025
42b8077
fixes
mohamed-yasser23 Dec 11, 2025
04e0d8d
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 11, 2025
e5f335d
doooooone
mohamed-yasser23 Dec 12, 2025
63f8916
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 12, 2025
d962b63
some fixes
mohamed-yasser23 Dec 14, 2025
f9791b2
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 14, 2025
8c64169
fixes
mohamed-yasser23 Dec 14, 2025
96ae1e3
fixes
mohamed-yasser23 Dec 14, 2025
4934253
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 14, 2025
69d162a
fixes
mohamed-yasser23 Dec 15, 2025
b82ceac
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 15, 2025
63da329
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 15, 2025
69b53eb
unit tests
mohamed-yasser23 Dec 15, 2025
50ece81
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 15, 2025
a320b97
tests
mohamed-yasser23 Dec 15, 2025
81488ed
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 15, 2025
0cede47
fixes
mohamed-yasser23 Dec 15, 2025
2fdf492
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 15, 2025
58b1eb2
fixes
mohamed-yasser23 Dec 15, 2025
7b7a8c0
hello
mohamed-yasser23 Dec 15, 2025
dae3636
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 15, 2025
9569030
fixes
mohamed-yasser23 Dec 15, 2025
0ffde78
tests
mohamed-yasser23 Dec 16, 2025
cdd6a2f
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 16, 2025
ee1ad08
Merge branch 'dev' into Feature/explore-search
mohamed-yasser23 Dec 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lam7a/lib/features/Explore/services/explore_api_service.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// coverage:ignore-file

import 'package:lam7a/features/Explore/model/trending_hashtag.dart';
import 'package:lam7a/core/services/api_service.dart';
import 'package:lam7a/core/models/user_model.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// coverage:ignore-file

import 'explore_api_service.dart';
import '../../../core/services/api_service.dart';
import '../../../core/models/user_model.dart';
Expand Down
2 changes: 2 additions & 0 deletions lam7a/lib/features/Explore/services/search_api_service.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// coverage:ignore-file

import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../../core/services/api_service.dart';
import 'package:lam7a/features/common/models/tweet_model.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// coverage:ignore-file

import 'search_api_service.dart';
import '../../../core/services/api_service.dart';
import '../../../core/models/user_model.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,6 @@ class YourAccountSettings extends ConsumerWidget {
);
},
),

// SettingsOptionTile(
// key: const ValueKey('openDeactivateAccountTile'),
// icon: Icons.favorite_border_rounded,
// title: 'Deactivate Account',
// subtitle: 'Find out how you can deactivate your account.',
// onTap: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (ctx) => const DeactivateAccountView(
// key: ValueKey('deactivateAccountPage'),
// ),
// ),
// );
// },
// ),
],
),
),
Expand Down
319 changes: 319 additions & 0 deletions lam7a/test/settings/ui/change_password_view_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mocktail/mocktail.dart';

import 'package:lam7a/features/settings/ui/view/account_settings/change_password/change_password_view.dart';
import 'package:lam7a/features/settings/ui/viewmodel/change_password_viewmodel.dart';
import 'package:lam7a/features/settings/ui/viewmodel/account_viewmodel.dart';
import 'package:lam7a/features/settings/repository/account_settings_repository.dart';
import 'package:lam7a/core/models/user_model.dart';

class FakeAssetBundle extends CachingAssetBundle {
@override
Future<ByteData> load(String key) async {
return ByteData.view(Uint8List(0).buffer);
}

@override
Future<T> loadStructuredBinaryData<T>(
String key,
FutureOr<T> Function(ByteData data) parser,
) async {
final emptyManifest = const StandardMessageCodec().encodeMessage(
<String, dynamic>{},
);
return parser(ByteData.view(emptyManifest!.buffer));
}
}

class MockAccountSettingsRepository extends Mock
implements AccountSettingsRepository {}

class FakeAccountViewModel extends AccountViewModel {
final AccountSettingsRepository repo;
FakeAccountViewModel(this.repo);

@override
UserModel build() => UserModel(
username: 'test_user',
email: 'test@mail.com',
role: '',
name: '',
birthDate: '',
profileImageUrl: '',
bannerImageUrl: '',
bio: '',
location: '',
website: '',
createdAt: '',
);
}

Widget createTestWidget(MockAccountSettingsRepository mockRepo) {
return ProviderScope(
overrides: [
accountSettingsRepoProvider.overrideWithValue(mockRepo),
accountProvider.overrideWith(() => FakeAccountViewModel(mockRepo)),
],
child: DefaultAssetBundle(
bundle: FakeAssetBundle(),
child: MaterialApp(
home: const ChangePasswordView(),
routes: {
'/forgot_password': (context) => const Scaffold(
body: Center(child: Text('Forgot Password Screen')),
),
},
),
),
);
}

void main() {
late MockAccountSettingsRepository mockRepo;

setUp(() {
mockRepo = MockAccountSettingsRepository();
});

testWidgets('renders all password fields and submit button', (tester) async {
await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

expect(
find.byKey(const Key('change_password_current_field')),
findsOneWidget,
);
expect(find.byKey(const Key('change_password_new_field')), findsOneWidget);
expect(
find.byKey(const Key('change_password_confirm_field')),
findsOneWidget,
);
expect(
find.byKey(const Key('change_password_submit_button')),
findsOneWidget,
);
expect(
find.byKey(const Key('change_password_forgot_button')),
findsOneWidget,
);
expect(find.text('test_user'), findsOneWidget);
});

testWidgets('password fields are obscured by default', (tester) async {
await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

final currentField = tester.widget<TextField>(
find.descendant(
of: find.byKey(const Key('change_password_current_field')),
matching: find.byType(TextField),
),
);
final newField = tester.widget<TextField>(
find.descendant(
of: find.byKey(const Key('change_password_new_field')),
matching: find.byType(TextField),
),
);
final confirmField = tester.widget<TextField>(
find.descendant(
of: find.byKey(const Key('change_password_confirm_field')),
matching: find.byType(TextField),
),
);

expect(currentField.obscureText, isTrue);
expect(newField.obscureText, isTrue);
expect(confirmField.obscureText, isTrue);
});

testWidgets('submit button is disabled when fields are empty', (
tester,
) async {
await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

final button = tester.widget<FilledButton>(
find.byKey(const Key('change_password_submit_button')),
);
expect(button.onPressed, isNull);
});

testWidgets('submit button enabled when all fields valid', (tester) async {
await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

await tester.enterText(
find.byKey(const Key('change_password_current_field')),
'OldPass123!',
);
await tester.enterText(
find.byKey(const Key('change_password_new_field')),
'NewPass123!',
);
await tester.enterText(
find.byKey(const Key('change_password_confirm_field')),
'NewPass123!',
);
await tester.pumpAndSettle();

final button = tester.widget<FilledButton>(
find.byKey(const Key('change_password_submit_button')),
);
expect(button.onPressed, isNotNull);
});

testWidgets('shows error when new password is too short', (tester) async {
await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

await tester.enterText(
find.byKey(const Key('change_password_new_field')),
'Short1!',
);

// Trigger focus change to validate
await tester.tap(find.byKey(const Key('change_password_confirm_field')));
await tester.pumpAndSettle();

expect(find.text('Password must be at least 8 characters'), findsOneWidget);
});

testWidgets('shows error when passwords do not match', (tester) async {
await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

await tester.enterText(
find.byKey(const Key('change_password_new_field')),
'NewPass123!',
);
await tester.enterText(
find.byKey(const Key('change_password_confirm_field')),
'DifferentPass123!',
);

// Trigger focus change
await tester.tap(find.byKey(const Key('change_password_current_field')));
await tester.pumpAndSettle();

expect(find.text('Passwords do not match'), findsOneWidget);
});

testWidgets('shows error when new password missing required characters', (
tester,
) async {
await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

await tester.enterText(
find.byKey(const Key('change_password_new_field')),
'onlylowercase',
);

await tester.tap(find.byKey(const Key('change_password_confirm_field')));
await tester.pumpAndSettle();

expect(find.textContaining('Password must include:'), findsOneWidget);
});

testWidgets('successful password change shows snackbar and pops', (
tester,
) async {
when(() => mockRepo.changePassword(any(), any())).thenAnswer((_) async {});

await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

await tester.enterText(
find.byKey(const Key('change_password_current_field')),
'OldPass123!',
);
await tester.enterText(
find.byKey(const Key('change_password_new_field')),
'NewPass123!',
);
await tester.enterText(
find.byKey(const Key('change_password_confirm_field')),
'NewPass123!',
);
await tester.pumpAndSettle();

await tester.tap(find.byKey(const Key('change_password_submit_button')));
await tester.pumpAndSettle();

expect(find.text('Password changed successfully'), findsOneWidget);
verify(
() => mockRepo.changePassword('OldPass123!', 'NewPass123!'),
).called(1);
});

testWidgets('failed password change shows error dialog', (tester) async {
when(
() => mockRepo.changePassword(any(), any()),
).thenThrow(Exception('Invalid password'));

await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

await tester.enterText(
find.byKey(const Key('change_password_current_field')),
'WrongPass123!',
);
await tester.enterText(
find.byKey(const Key('change_password_new_field')),
'NewPass123!',
);
await tester.enterText(
find.byKey(const Key('change_password_confirm_field')),
'NewPass123!',
);
await tester.pumpAndSettle();

await tester.tap(find.byKey(const Key('change_password_submit_button')));
await tester.pumpAndSettle();

expect(find.text('Incorrect Password'), findsOneWidget);
expect(
find.text('Please enter the correct current password.'),
findsOneWidget,
);

await tester.tap(find.text('OK'));
await tester.pumpAndSettle();

verify(
() => mockRepo.changePassword('WrongPass123!', 'NewPass123!'),
).called(1);
});

testWidgets('toggle visibility icons work correctly', (tester) async {
await tester.pumpWidget(createTestWidget(mockRepo));
await tester.pumpAndSettle();

// Find visibility toggle for current password field
final visibilityIcons = find.byIcon(Icons.visibility_off);
expect(visibilityIcons, findsNWidgets(3));

// Tap first toggle (current password)
await tester.tap(visibilityIcons.first);
await tester.pumpAndSettle();

expect(find.byIcon(Icons.visibility), findsOneWidget);
expect(find.byIcon(Icons.visibility_off), findsNWidgets(2));
});

// testWidgets('forgot password button navigates correctly', (tester) async {
// await tester.pumpWidget(createTestWidget(mockRepo));
// await tester.pumpAndSettle();

// await tester.tap(find.byKey(const Key('change_password_forgot_button')));
// await tester.pumpAndSettle();

// expect(find.text('Forgot Password Screen'), findsOneWidget);
// });
}
Loading
Loading