Commit 2b5b7f13 authored by Vinícius C's avatar Vinícius C
Browse files

Sp3 start

parent 89a9f3e5
Pipeline #19151 canceled with stage
Showing with 863 additions and 181 deletions
+863 -181
......@@ -67,7 +67,6 @@ test_with_coverage:
echo "Minimum required coverage: $MINIMUM_COVERAGE%"
fi
coverage: '/lines\.*: \d+\.\d+\%/'
allow_failure: true
artifacts:
paths:
- coverage
......
......@@ -13,15 +13,3 @@ analyzer:
- dart_code_metrics
exclude:
- test/full_coverage_test.dart
dart_code_metrics:
anti-patterns:
- long-method
- long-parameter-list
metrics:
cyclomatic-complexity: 20
number-of-parameters: 8
maximum-nesting-level: 4
source-lines-of-code: 80
metrics-exclude:
- test/**
File added
import json
import sys
drop_until_brace = lambda s: s[:s.rfind('}')+1] if '}' in s else s
def check_for_issues(json_file):
try:
with open(json_file, 'r') as file:
found_issue = False
for line in enumerate(file, 1):
try:
clean_line = drop_until_brace(line[1])
data = json.loads(clean_line)
if data.get('type') == 'issue':
print('\n')
print("Issue found: ", data.get('description'))
print("Severity: ", data.get('severity'))
print("Location: ", data.get('location'))
print("Position: ", data.get('position'))
print("\n")
found_issue = True
except:
continue
if not found_issue:
print("No issues found.")
return 0
return 1
except FileNotFoundError:
print(f"Error: File {json_file} not found.")
return 1
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python check_issues.py <path_to_json_file>")
sys.exit(1)
result = check_for_issues(sys.argv[1])
sys.exit(result)
......@@ -30,7 +30,7 @@ class AppRouter {
path: home,
pageBuilder: (BuildContext context, GoRouterState state) {
return const NoTransitionPage(
child: Text('Home'),
child: Text('Home placeholder'),
);
},
),
......
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class AppTheme {
static const Color _defaultPrimaryColor = Color(0xFF9BD1EB);
class ThemeColors {
final FlexSchemeColor lightColors;
final FlexSchemeColor darkColors;
static ThemeData _createTheme(Color primaryColor, Brightness brightness) {
final ColorScheme colorScheme = ColorScheme.fromSeed(
seedColor: _defaultPrimaryColor,
brightness: brightness,
);
const ThemeColors({
required this.lightColors,
required this.darkColors,
});
}
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
appBarTheme: AppBarTheme(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
),
textTheme: GoogleFonts.fredokaTextTheme(
brightness == Brightness.light
? ThemeData.light().textTheme
: ThemeData.dark().textTheme,
class ThemeCollection {
static const ThemeColors clarityDefault = ThemeColors(
lightColors: FlexSchemeColor(
primary: Color.fromRGBO(155, 209, 232, 1.0),
primaryContainer: Color.fromRGBO(155, 209, 232, 1.0),
secondary: Color.fromRGBO(246, 244, 245, 1.0),
secondaryContainer: Color.fromRGBO(202, 240, 248, 1.0),
tertiary: Color.fromRGBO(202, 240, 248, 1.0),
tertiaryContainer: Color.fromRGBO(34, 34, 34, 1.0),
appBarColor: Color.fromRGBO(202, 240, 248, 1.0),
error: Color(0xffb00020),
),
darkColors: FlexSchemeColor(
primary: Color(0xff0f0b21),
primaryContainer: Color(0xff363457),
secondary: Color(0xffffffff),
secondaryContainer: Color(0xff363457),
tertiary: Color(0xff735290),
tertiaryContainer: Color(0xff1e1e1e),
appBarColor: Color(0xff363457),
error: Color(0xffcf6679),
),
);
}
class AppTheme {
static ThemeData light({ThemeColors theme = ThemeCollection.clarityDefault}) {
return FlexThemeData.light(
colors: theme.lightColors,
surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
blendLevel: 7,
subThemesData: const FlexSubThemesData(
blendOnLevel: 10,
blendOnColors: false,
useTextTheme: true,
useM2StyleDividerInM3: true,
alignedDropdown: true,
useInputDecoratorThemeInDialogs: true,
),
visualDensity: FlexColorScheme.comfortablePlatformDensity,
useMaterial3: true,
swapLegacyOnMaterial3: true,
textTheme: GoogleFonts.fredokaTextTheme(ThemeData.light().textTheme),
);
}
static ThemeData light({Color primaryColor = _defaultPrimaryColor}) {
return _createTheme(primaryColor, Brightness.light);
}
static ThemeData dark({Color primaryColor = _defaultPrimaryColor}) {
return _createTheme(primaryColor, Brightness.dark);
static ThemeData dark({ThemeColors theme = ThemeCollection.clarityDefault}) {
return FlexThemeData.dark(
colors: theme.darkColors,
surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
blendLevel: 13,
subThemesData: const FlexSubThemesData(
blendOnLevel: 20,
useTextTheme: true,
useM2StyleDividerInM3: true,
alignedDropdown: true,
useInputDecoratorThemeInDialogs: true,
),
visualDensity: FlexColorScheme.comfortablePlatformDensity,
useMaterial3: true,
swapLegacyOnMaterial3: true,
textTheme: GoogleFonts.fredokaTextTheme(ThemeData.dark().textTheme),
);
}
}
class ThemeNotifier extends ChangeNotifier {
ThemeMode _themeMode = ThemeMode.system;
Color _primaryColor = AppTheme._defaultPrimaryColor;
ThemeColors _themeColors = ThemeCollection.clarityDefault;
ThemeMode get themeMode => _themeMode;
Color get primaryColor => _primaryColor;
ThemeColors get themeColors => _themeColors;
void setThemeMode(ThemeMode mode) {
_themeMode = mode;
notifyListeners();
}
void setPrimaryColor(Color color) {
_primaryColor = color;
void setThemeColors(ThemeColors themeColors) {
_themeColors = themeColors;
notifyListeners();
}
}
......@@ -7,7 +7,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class CourseBloc extends Bloc<CourseEvent, CourseState> {
final ICourseRepository courseRepository;
CourseBloc({required this.courseRepository}) : super(const CourseListInital()) {
CourseBloc({required this.courseRepository})
: super(const CourseListInital()) {
on<LoadCourseList>(_loadCourseList);
on<UpdateSearchTerm>(_updateSearchTerm);
}
......
......@@ -30,11 +30,11 @@ class ClarityApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => ThemeNotifier()),
],
builder: (context, child) {
final themeNotifier = Provider.of<ThemeNotifier>(context);
Provider.of<ThemeNotifier>(context);
return MaterialApp.router(
title: 'Clarity',
theme: AppTheme.light(primaryColor: themeNotifier.primaryColor),
darkTheme: AppTheme.dark(primaryColor: themeNotifier.primaryColor),
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
routerConfig: AppRouter.router,
);
},
......
......@@ -8,6 +8,7 @@ environment:
dependencies:
cupertino_icons: ^1.0.8
equatable: ^2.0.5
flex_color_scheme: ^7.3.1
flutter:
sdk: flutter
flutter_bloc: ^8.1.6
......@@ -20,6 +21,7 @@ dependencies:
dev_dependencies:
bloc_test: ^9.1.7
build_runner: ^2.4.13
flutter_test:
sdk: flutter
full_coverage: ^1.0.0
......@@ -30,3 +32,8 @@ flutter:
uses-material-design: true
assets:
- assets/images/
- assets/fonts/
fonts:
- family: Fredoka
fonts:
- asset: assets/fonts/Fredoka-Regular.ttf
import 'package:clarity_frontend/config/app_router.dart';
import 'package:clarity_frontend/features/courses/bloc/course_bloc.dart';
import 'package:clarity_frontend/features/courses/bloc/course_state.dart';
import 'package:clarity_frontend/features/courses/presentation/screens/course_list_screen.dart';
import 'package:clarity_frontend/features/onboarding/onboarding_handler.dart';
import 'package:clarity_frontend/main_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'app_router_test.mocks.dart';
@GenerateNiceMocks([MockSpec<CourseBloc>()])
void main() {
group('AppRouter', () {
late GoRouter router;
late MockCourseBloc mockCourseBloc;
setUp(() async {
router = AppRouter.router;
mockCourseBloc = MockCourseBloc();
when(mockCourseBloc.state).thenReturn(const CourseListInital());
});
Widget createWidgetUnderTest() {
return BlocProvider<CourseBloc>.value(
value: mockCourseBloc,
child: MaterialApp.router(
routerConfig: router,
),
);
}
testWidgets('should navigate to onboarding route',
(WidgetTester tester) async {
// Act
await tester.pumpWidget(
createWidgetUnderTest(),
);
await tester.pumpAndSettle();
// Assert
expect(find.byType(OnboardingHandler), findsOneWidget);
});
testWidgets('should navigate to home route', (WidgetTester tester) async {
// Arrange
router.go(AppRouter.home);
// Act
await tester.pumpWidget(
createWidgetUnderTest(),
);
await tester.pumpAndSettle();
// Assert
expect(find.text('Home placeholder'), findsOneWidget);
});
testWidgets('should navigate to courses route with CourseBloc provided',
(WidgetTester tester) async {
// Arrange
router.go(AppRouter.courses);
// Act
await tester.pumpWidget(
createWidgetUnderTest(),
);
await tester.pumpAndSettle();
// Assert
expect(find.byType(CourseListScreen), findsOneWidget);
final courseBloc = BlocProvider.of<CourseBloc>(
tester.element(find.byType(CourseListScreen)),
);
expect(courseBloc, mockCourseBloc);
});
testWidgets('should navigate to profile route',
(WidgetTester tester) async {
// Arrange
router.go(AppRouter.profile);
// Act
await tester.pumpWidget(
createWidgetUnderTest(),
);
await tester.pumpAndSettle();
// Assert
expect(find.text('perfil'), findsOneWidget);
});
testWidgets('should display error page for unknown route',
(WidgetTester tester) async {
// Arrange
router.go('/unknown');
// Act
await tester.pumpWidget(
createWidgetUnderTest(),
);
await tester.pumpAndSettle();
// Assert
expect(find.text('Sem rota definida'), findsOneWidget);
});
testWidgets('should build MainScreen when navigating to shell routes',
(WidgetTester tester) async {
// Arrange
router.go(AppRouter.home);
// Act
await tester.pumpWidget(
createWidgetUnderTest(),
);
await tester.pumpAndSettle();
// Assert
expect(find.byType(MainScreen), findsOneWidget);
});
});
}
// Mocks generated by Mockito 5.4.4 from annotations
// in clarity_frontend/test/config/app_router_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i5;
import 'package:clarity_frontend/core/data/interfaces/i_course_repository.dart'
as _i2;
import 'package:clarity_frontend/features/courses/bloc/course_bloc.dart' as _i4;
import 'package:clarity_frontend/features/courses/bloc/course_event.dart'
as _i6;
import 'package:clarity_frontend/features/courses/bloc/course_state.dart'
as _i3;
import 'package:flutter_bloc/flutter_bloc.dart' as _i7;
import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeICourseRepository_0<TCourse> extends _i1.SmartFake
implements _i2.ICourseRepository<TCourse> {
_FakeICourseRepository_0(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeCourseState_1 extends _i1.SmartFake implements _i3.CourseState {
_FakeCourseState_1(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
/// A class which mocks [CourseBloc].
///
/// See the documentation for Mockito's code generation for more information.
class MockCourseBloc extends _i1.Mock implements _i4.CourseBloc {
@override
_i2.ICourseRepository<dynamic> get courseRepository => (super.noSuchMethod(
Invocation.getter(#courseRepository),
returnValue: _FakeICourseRepository_0<dynamic>(
this,
Invocation.getter(#courseRepository),
),
returnValueForMissingStub: _FakeICourseRepository_0<dynamic>(
this,
Invocation.getter(#courseRepository),
),
) as _i2.ICourseRepository<dynamic>);
@override
_i3.CourseState get state => (super.noSuchMethod(
Invocation.getter(#state),
returnValue: _FakeCourseState_1(
this,
Invocation.getter(#state),
),
returnValueForMissingStub: _FakeCourseState_1(
this,
Invocation.getter(#state),
),
) as _i3.CourseState);
@override
_i5.Stream<_i3.CourseState> get stream => (super.noSuchMethod(
Invocation.getter(#stream),
returnValue: _i5.Stream<_i3.CourseState>.empty(),
returnValueForMissingStub: _i5.Stream<_i3.CourseState>.empty(),
) as _i5.Stream<_i3.CourseState>);
@override
bool get isClosed => (super.noSuchMethod(
Invocation.getter(#isClosed),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
void add(_i6.CourseEvent? event) => super.noSuchMethod(
Invocation.method(
#add,
[event],
),
returnValueForMissingStub: null,
);
@override
void onEvent(_i6.CourseEvent? event) => super.noSuchMethod(
Invocation.method(
#onEvent,
[event],
),
returnValueForMissingStub: null,
);
@override
void emit(_i3.CourseState? state) => super.noSuchMethod(
Invocation.method(
#emit,
[state],
),
returnValueForMissingStub: null,
);
@override
void on<E extends _i6.CourseEvent>(
_i7.EventHandler<E, _i3.CourseState>? handler, {
_i7.EventTransformer<E>? transformer,
}) =>
super.noSuchMethod(
Invocation.method(
#on,
[handler],
{#transformer: transformer},
),
returnValueForMissingStub: null,
);
@override
void onTransition(
_i7.Transition<_i6.CourseEvent, _i3.CourseState>? transition) =>
super.noSuchMethod(
Invocation.method(
#onTransition,
[transition],
),
returnValueForMissingStub: null,
);
@override
_i5.Future<void> close() => (super.noSuchMethod(
Invocation.method(
#close,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
void onChange(_i7.Change<_i3.CourseState>? change) => super.noSuchMethod(
Invocation.method(
#onChange,
[change],
),
returnValueForMissingStub: null,
);
@override
void addError(
Object? error, [
StackTrace? stackTrace,
]) =>
super.noSuchMethod(
Invocation.method(
#addError,
[
error,
stackTrace,
],
),
returnValueForMissingStub: null,
);
@override
void onError(
Object? error,
StackTrace? stackTrace,
) =>
super.noSuchMethod(
Invocation.method(
#onError,
[
error,
stackTrace,
],
),
returnValueForMissingStub: null,
);
}
import 'package:clarity_frontend/config/themes.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
GoogleFonts.config.allowRuntimeFetching = false;
group('AppTheme', () {
test('light theme should not be null', () {
final themeData = AppTheme.light();
expect(themeData, isNotNull);
expect(
themeData.primaryColor,
equals(ThemeCollection.clarityDefault.lightColors.primary),
);
});
test('dark theme should not be null', () {
final themeData = AppTheme.dark();
expect(themeData, isNotNull);
expect(
themeData.primaryColor,
equals(ThemeCollection.clarityDefault.darkColors.primary),
);
});
});
group('ThemeNotifier', () {
test('default theme mode is system', () {
final themeNotifier = ThemeNotifier();
expect(themeNotifier.themeMode, equals(ThemeMode.system));
});
test('can change theme mode', () {
final themeNotifier = ThemeNotifier();
themeNotifier.setThemeMode(ThemeMode.dark);
expect(themeNotifier.themeMode, equals(ThemeMode.dark));
});
test('can change theme colors', () {
final themeNotifier = ThemeNotifier();
final newThemeColors = ThemeColors(
lightColors: FlexSchemeColor(
primary: Colors.red,
primaryContainer: Colors.red[100],
secondary: Colors.blue,
secondaryContainer: Colors.blue[100],
tertiary: Colors.green,
tertiaryContainer: Colors.green[100],
appBarColor: Colors.yellow,
error: Colors.redAccent,
),
darkColors: FlexSchemeColor(
primary: Colors.purple,
primaryContainer: Colors.purple[100],
secondary: Colors.orange,
secondaryContainer: Colors.orange[100],
tertiary: Colors.pink,
tertiaryContainer: Colors.pink[100],
appBarColor: Colors.black,
error: Colors.redAccent,
),
);
themeNotifier.setThemeColors(newThemeColors);
expect(themeNotifier.themeColors, equals(newThemeColors));
});
});
}
......@@ -82,49 +82,49 @@ void main() {
),
],
);
blocTest<CourseBloc, CourseState>(
'should emit [CourseListLoaded] with filtered courses when UpdateSearchTerm is added',
build: () {
when(mockCourseRepository.getAllCourses()).thenAnswer((_) async => courses);
return courseBloc;
},
seed: () => CourseListLoaded(
allCourses: courses,
filteredCourses: courses,
),
act: (bloc) => bloc.add(const UpdateSearchTerm('Test Course 1')),
expect: () => [
isA<CourseListLoaded>().having(
(state) => state.filteredCourses.length,
'filteredCourses length',
equals(1),
blocTest<CourseBloc, CourseState>(
'should emit [CourseListLoaded] with filtered courses when UpdateSearchTerm is added',
build: () {
when(mockCourseRepository.getAllCourses())
.thenAnswer((_) async => courses);
return courseBloc;
},
seed: () => CourseListLoaded(
allCourses: courses,
filteredCourses: courses,
),
],
);
act: (bloc) => bloc.add(const UpdateSearchTerm('Test Course 1')),
expect: () => [
isA<CourseListLoaded>().having(
(state) => state.filteredCourses.length,
'filteredCourses length',
equals(1),
),
],
);
blocTest<CourseBloc, CourseState>(
'should emit [CourseListLoading, CourseListError] with searchTerm when LoadCourseList fails',
build: () {
when(mockCourseRepository.getAllCourses())
.thenThrow(Exception('Mock Exception'));
return courseBloc;
},
seed: () => CourseListLoaded(
allCourses: courses,
filteredCourses: courses,
searchTerm: 'Test',
),
act: (bloc) => bloc.add(LoadCourseList()),
expect: () => [
isA<CourseListLoading>().having((state) => state.searchTerm, 'searchTerm', equals('Test')),
isA<CourseListError>().having(
(state) => state.errorMessage,
'errorMessage',
equals('Exception: Mock Exception'),
blocTest<CourseBloc, CourseState>(
'should emit [CourseListLoading, CourseListError] with searchTerm when LoadCourseList fails',
build: () {
when(mockCourseRepository.getAllCourses())
.thenThrow(Exception('Mock Exception'));
return courseBloc;
},
seed: () => CourseListLoaded(
allCourses: courses,
filteredCourses: courses,
searchTerm: 'Test',
),
],
);
act: (bloc) => bloc.add(LoadCourseList()),
expect: () => [
isA<CourseListLoading>()
.having((state) => state.searchTerm, 'searchTerm', equals('Test')),
isA<CourseListError>().having(
(state) => state.errorMessage,
'errorMessage',
equals('Exception: Mock Exception'),
),
],
);
});
}
import 'dart:async' as i3;
// Mocks generated by Mockito 5.4.4 from annotations
// in clarity_frontend/test/courses/bloc/course_bloc_test.dart.
// Do not manually edit this file.
import 'package:clarity_frontend/core/data/interfaces/i_course_repository.dart' as i2;
import 'package:clarity_frontend/core/data/models/course.dart' as i4;
import 'package:mockito/mockito.dart' as i1;
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i3;
class MockICourseRepository extends i1.Mock implements i2.ICourseRepository {
import 'package:clarity_frontend/core/data/interfaces/i_course_repository.dart'
as _i2;
import 'package:clarity_frontend/core/data/models/course.dart' as _i4;
import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
/// A class which mocks [ICourseRepository].
///
/// See the documentation for Mockito's code generation for more information.
class MockICourseRepository<TCourse> extends _i1.Mock
implements _i2.ICourseRepository<TCourse> {
MockICourseRepository() {
i1.throwOnMissingStub(this);
_i1.throwOnMissingStub(this);
}
@override
i3.Future<List<i4.Course>> getAllCourses() => super.noSuchMethod(
@override
_i3.Future<List<_i4.Course>> getAllCourses() => (super.noSuchMethod(
Invocation.method(
#getAllCourses,
[],
),
returnValue: i3.Future<List<i4.Course>>.value(<i4.Course>[]),
) as i3.Future<List<i4.Course>>;
returnValue: _i3.Future<List<_i4.Course>>.value(<_i4.Course>[]),
) as _i3.Future<List<_i4.Course>>);
}
......@@ -5,7 +5,7 @@ void main() {
group('CourseState', () {
test('CourseListInitial should contain empty string in props', () {
const state = CourseListInital();
expect(state.props, equals(['']));
expect(state.props, equals(['']));
});
test('CourseListLoading should be in loading state', () {
......@@ -21,7 +21,7 @@ void main() {
test('CourseListInitial props should contain empty string', () {
const state = CourseListInital();
expect(state.props, equals(['']));
expect(state.props, equals(['']));
});
});
}
......@@ -111,7 +111,8 @@ void main() {
const Course(id: 2, name: 'Test Course 2', level: 2, progress: 75),
];
when(mockCourseBloc.state).thenReturn(
CourseListLoaded(allCourses: courses, filteredCourses: courses),);
CourseListLoaded(allCourses: courses, filteredCourses: courses),
);
// Como a widget tem um ListView (uma lista que expande inifinitamente), precisamos definir o tamanho da tela
tester.view.physicalSize = const Size(800, 600);
tester.view.devicePixelRatio = 1.0;
......@@ -129,33 +130,40 @@ void main() {
testWidgets(
'should render search bar',
(WidgetTester tester) async {
when(mockCourseBloc.state).thenReturn(const CourseListLoaded(
allCourses: [],
filteredCourses: [],
),);
when(mockCourseBloc.state).thenReturn(
const CourseListLoaded(
allCourses: [],
filteredCourses: [],
),
);
await tester.pumpWidget(createWidgetUnderTest());
expect(find.byType(TextFormField), findsOneWidget);
},
);
testWidgets(
'should display welcome message with username',
(WidgetTester tester) async {
when(mockCourseBloc.state).thenReturn(const CourseListLoaded(
allCourses: [],
filteredCourses: [],
),);
await tester.pumpWidget(createWidgetUnderTest());
await tester.pumpAndSettle();
final welcomeTextFinder = find.byWidgetPredicate((widget) =>
widget is RichText &&
widget.text.toPlainText() == "Olá (usuário), você já pode acessar os cursos abaixo!",);
expect(welcomeTextFinder, findsOneWidget);
},
);
testWidgets(
'should display welcome message with username',
(WidgetTester tester) async {
when(mockCourseBloc.state).thenReturn(
const CourseListLoaded(
allCourses: [],
filteredCourses: [],
),
);
await tester.pumpWidget(createWidgetUnderTest());
await tester.pumpAndSettle();
final welcomeTextFinder = find.byWidgetPredicate(
(widget) =>
widget is RichText &&
widget.text.toPlainText() ==
"Olá (usuário), você já pode acessar os cursos abaixo!",
);
expect(welcomeTextFinder, findsOneWidget);
},
);
testWidgets('should trigger LoadCourseList event on initial build',
(WidgetTester tester) async {
when(mockCourseBloc.state).thenReturn(const CourseListInital());
......
// Mocks generated by Mockito 5.4.4 from annotations
// in clarity_frontend/test/features/courses/presentation/course_list_screen_test.dart.
// in clarity_frontend/test/courses/presentation/course_list_screen_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
......
......@@ -3,10 +3,10 @@ import 'package:clarity_frontend/features/courses/presentation/widgets/courses_l
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('CoursesList', () {
testWidgets('should display correct number of courses', (WidgetTester tester) async {
testWidgets('should display correct number of courses',
(WidgetTester tester) async {
// Arrange
final courses = [
const Course(id: 1, name: 'Test Course 1', level: 1, progress: 50),
......@@ -14,11 +14,13 @@ void main() {
];
// Act
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: CoursesList(courses: courses),
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CoursesList(courses: courses),
),
),
),);
);
// Assert
expect(find.byType(ListTile), findsNWidgets(courses.length));
......@@ -31,29 +33,33 @@ void main() {
];
// Act
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: CoursesList(courses: courses),
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CoursesList(courses: courses),
),
),
),);
);
// Assert
expect(find.text('Test Course 1'), findsOneWidget);
});
testWidgets('should display linear progress indicator', (WidgetTester tester) async {
testWidgets('should display linear progress indicator',
(WidgetTester tester) async {
// Arrange
final courses = [
const Course(id: 1, name: 'Test Course 1', level: 1, progress: 50),
];
// Act
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: CoursesList(courses: courses),
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CoursesList(courses: courses),
),
),
),);
);
// Assert
expect(find.byType(LinearProgressIndicator), findsOneWidget);
......
import 'package:clarity_frontend/config/app_router.dart';
import 'package:clarity_frontend/main_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
void main() {
group('MainScreen', () {
late GoRouter router;
setUp(() {
router = GoRouter(
initialLocation: AppRouter.home,
routes: [
ShellRoute(
builder: (context, state, child) => MainScreen(child: child),
routes: [
GoRoute(
path: AppRouter.home,
builder: (context, state) =>
const Scaffold(body: Text('Home Page')),
),
GoRoute(
path: AppRouter.courses,
builder: (context, state) =>
const Scaffold(body: Text('Courses Page')),
),
GoRoute(
path: AppRouter.profile,
builder: (context, state) =>
const Scaffold(body: Text('Profile Page')),
),
],
),
],
);
});
testWidgets('should build without errors', (WidgetTester tester) async {
// Act
await tester.pumpWidget(
MaterialApp.router(
routerConfig: router,
),
);
await tester.pumpAndSettle();
// Assert
expect(find.byType(MainScreen), findsOneWidget);
});
testWidgets('should display correct number of navigation destinations',
(WidgetTester tester) async {
// Act
await tester.pumpWidget(
MaterialApp.router(
routerConfig: router,
),
);
await tester.pumpAndSettle();
// Assert
final navBarFinder = find.byType(NavigationBar);
expect(navBarFinder, findsOneWidget);
final navDestinationsFinder = find.byType(NavigationDestination);
expect(navDestinationsFinder, findsNWidgets(3));
});
testWidgets('should calculate selected index correctly',
(WidgetTester tester) async {
// Arrange
router.go(AppRouter.profile);
// Act
await tester.pumpWidget(
MaterialApp.router(
routerConfig: router,
),
);
await tester.pumpAndSettle();
// Assert
final navBarFinder = find.byType(NavigationBar);
expect(navBarFinder, findsOneWidget);
final navigationBar = tester.widget<NavigationBar>(navBarFinder);
expect(navigationBar.selectedIndex, 2);
});
testWidgets('should navigate to correct route on navigation item tap',
(WidgetTester tester) async {
// Act
await tester.pumpWidget(
MaterialApp.router(
routerConfig: router,
),
);
await tester.pumpAndSettle();
final cursosNavItem = find.text('Cursos');
expect(cursosNavItem, findsOneWidget);
await tester.tap(cursosNavItem);
await tester.pumpAndSettle();
// Assert
expect(find.text('Courses Page'), findsOneWidget);
});
testWidgets('should update selected index when navigation item is tapped',
(WidgetTester tester) async {
// Act
await tester.pumpWidget(
MaterialApp.router(
routerConfig: router,
),
);
await tester.pumpAndSettle();
var navBar = tester.widget<NavigationBar>(find.byType(NavigationBar));
expect(navBar.selectedIndex, 1);
final perfilNavItem = find.text('Perfil');
await tester.tap(perfilNavItem);
await tester.pumpAndSettle();
// Assert
navBar = tester.widget<NavigationBar>(find.byType(NavigationBar));
expect(navBar.selectedIndex, 2);
expect(find.text('Profile Page'), findsOneWidget);
});
testWidgets('should display app bar with correct title image',
(WidgetTester tester) async {
// Act
await tester.pumpWidget(
MaterialApp.router(
routerConfig: router,
),
);
await tester.pumpAndSettle();
// Assert
final appBarFinder = find.byType(AppBar);
expect(appBarFinder, findsOneWidget);
final imageFinder = find.descendant(
of: appBarFinder,
matching: find.byType(Image),
);
expect(imageFinder, findsOneWidget);
final imageWidget = tester.widget<Image>(imageFinder);
expect(imageWidget.image, isA<AssetImage>());
final assetImage = imageWidget.image as AssetImage;
expect(assetImage.assetName, 'assets/images/logo_azul.png');
});
});
}
import 'package:clarity_frontend/config/app_router.dart';
import 'package:clarity_frontend/config/themes.dart';
import 'package:clarity_frontend/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
void main() {
group('ClarityApp', () {
testWidgets('should build without errors', (WidgetTester tester) async {
// Act
await tester.pumpWidget(const ClarityApp());
// Assert
expect(find.byType(ClarityApp), findsOneWidget);
expect(find.byType(MaterialApp), findsOneWidget);
});
testWidgets('should use MultiProvider', (WidgetTester tester) async {
// Act
await tester.pumpWidget(const ClarityApp());
// Assert
expect(find.byType(MultiProvider), findsOneWidget);
});
testWidgets('should provide ThemeNotifier', (WidgetTester tester) async {
// Act
await tester.pumpWidget(const ClarityApp());
// Find the Provider in the widget tree
final providerFinder = find.byWidgetPredicate((widget) {
return widget is InheritedProvider<ThemeNotifier>;
});
// Assert
expect(providerFinder, findsOneWidget);
});
testWidgets('should have correct app title', (WidgetTester tester) async {
// Act
await tester.pumpWidget(const ClarityApp());
// Assert
final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));
expect(materialApp.title, 'Clarity');
});
testWidgets('should have routerConfig set', (WidgetTester tester) async {
// Act
await tester.pumpWidget(const ClarityApp());
// Assert
final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));
expect(materialApp.routerConfig, AppRouter.router);
});
testWidgets('should use light and dark themes',
(WidgetTester tester) async {
// Arrange
final themeNotifier = ThemeNotifier();
// Act
await tester.pumpWidget(
ChangeNotifierProvider<ThemeNotifier>.value(
value: themeNotifier,
child: const ClarityApp(),
),
);
// Assert
final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));
expect(materialApp.theme!.brightness, Brightness.light);
expect(materialApp.darkTheme!.brightness, Brightness.dark);
});
});
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment