forked from eatvio/eatvio_app
Merge branch 'mso7nueh-main'
This commit is contained in:
55
analyze_coverage.py
Normal file
55
analyze_coverage.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
|
||||
def analyze_coverage():
|
||||
files = {}
|
||||
current_file = None
|
||||
|
||||
with open('coverage/lcov.info', 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if line.startswith('SF:'):
|
||||
current_file = line[3:]
|
||||
files[current_file] = {'total': 0, 'covered': 0}
|
||||
i += 1
|
||||
elif line.startswith('LF:') and current_file:
|
||||
files[current_file]['total'] = int(line[3:])
|
||||
i += 1
|
||||
elif line.startswith('LH:') and current_file:
|
||||
files[current_file]['covered'] = int(line[3:])
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# Filter out generated files
|
||||
filtered = []
|
||||
for f, d in files.items():
|
||||
if not f.endswith('.g.dart') and not 'generated' in f.lower() and not f.endswith('l10n.dart'):
|
||||
filtered.append((f, d))
|
||||
|
||||
# Find files with less than 50% coverage
|
||||
low_coverage = []
|
||||
for f, d in filtered:
|
||||
if d['total'] > 0:
|
||||
pct = d['covered']/d['total']*100
|
||||
if pct < 50:
|
||||
low_coverage.append((f, d))
|
||||
|
||||
# Sort by coverage percentage (lowest first)
|
||||
sorted_files = sorted(
|
||||
low_coverage,
|
||||
key=lambda x: (x[1]['covered']/x[1]['total']*100 if x[1]['total'] > 0 else 0)
|
||||
)
|
||||
|
||||
print('Files with less than 50% coverage:')
|
||||
print('=' * 80)
|
||||
for f, d in sorted_files[:50]:
|
||||
pct = d['covered']/d['total']*100
|
||||
print(f'{pct:6.1f}% ({d["covered"]:4d}/{d["total"]:4d}) - {f}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
analyze_coverage()
|
||||
|
||||
@@ -28,6 +28,8 @@ class _ChosePlanDateWidget extends State<ChosePlanDateWidget> {
|
||||
super.initState();
|
||||
if (widget.hasPersonalDiet) {
|
||||
startDate = [DateTime.now().add(const Duration(days: 2))];
|
||||
} else {
|
||||
startDate = [DateTime.now()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,23 +51,12 @@ class _ChosePlanDateWidget extends State<ChosePlanDateWidget> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
planName ?? '',
|
||||
style: TextStyles.headingXLSemibold.copyWith(
|
||||
color: appTheme.black,
|
||||
),
|
||||
),
|
||||
Text(planName ?? '', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||
if (widget.hasPersonalDiet) ...[
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
PortalTarget(
|
||||
closeDuration: const Duration(milliseconds: 20),
|
||||
anchor: const Aligned(
|
||||
shiftToWithinBound: AxisFlag(x: true, y: true),
|
||||
follower: Alignment.topRight,
|
||||
target: Alignment.bottomLeft,
|
||||
),
|
||||
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomLeft),
|
||||
portalFollower: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Container(
|
||||
@@ -73,24 +64,12 @@ class _ChosePlanDateWidget extends State<ChosePlanDateWidget> {
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: visible ? appTheme.whiteA700 : Colors.transparent,
|
||||
boxShadow: visible
|
||||
? [
|
||||
BoxShadow(
|
||||
color: appTheme.black900.withValues(alpha: 0.15),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
boxShadow: visible ? [BoxShadow(color: appTheme.black900.withValues(alpha: 0.15), blurRadius: 4, offset: const Offset(0, 4))] : null,
|
||||
),
|
||||
child: AnimatedDefaultTextStyle(
|
||||
style: TextStyles.textMMediumAuto.copyWith(
|
||||
color: visible ? appTheme.black : Colors.transparent,
|
||||
),
|
||||
style: TextStyles.textMMediumAuto.copyWith(color: visible ? appTheme.black : Colors.transparent),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
s.chosenPlanStartDateDescr,
|
||||
),
|
||||
child: Text(s.chosenPlanStartDateDescr),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -104,14 +83,11 @@ class _ChosePlanDateWidget extends State<ChosePlanDateWidget> {
|
||||
'assets/icons/question_mark.svg',
|
||||
width: 22,
|
||||
height: 22,
|
||||
colorFilter: ColorFilter.mode(
|
||||
appTheme.blueA400,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
)
|
||||
]
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -120,19 +96,12 @@ class _ChosePlanDateWidget extends State<ChosePlanDateWidget> {
|
||||
child: Text(
|
||||
s.chose_plan_start_date_2,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(
|
||||
color: appTheme.black,
|
||||
),
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: CustomDivider(),
|
||||
),
|
||||
CalendarWidget(
|
||||
startDate: startDate,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.symmetric(horizontal: 16.0), child: CustomDivider()),
|
||||
CalendarWidget(startDate: startDate),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 28),
|
||||
child: CustomOutlinedButton(
|
||||
@@ -150,13 +119,8 @@ class _ChosePlanDateWidget extends State<ChosePlanDateWidget> {
|
||||
},
|
||||
height: 60,
|
||||
text: s.confirm,
|
||||
buttonTextStyle: TextStyles.textLSemibold.copyWith(
|
||||
color: appTheme.whiteA700,
|
||||
),
|
||||
buttonStyle: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
),
|
||||
buttonTextStyle: TextStyles.textLSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), side: const WidgetStatePropertyAll(BorderSide.none)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -61,6 +61,559 @@ void main() {
|
||||
// onChanged должен быть вызван
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('отображает toolbar когда showInlineToolbar = true', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(showInlineToolbar: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
// Toolbar должен быть виден
|
||||
expect(find.byIcon(Icons.format_bold), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('скрывает toolbar когда showInlineToolbar = false', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(showInlineToolbar: false),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
// Toolbar не должен быть виден
|
||||
expect(find.byIcon(Icons.format_bold), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('применяет форматирование жирным', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test');
|
||||
await tester.pump();
|
||||
|
||||
// Нажимаем кнопку жирного
|
||||
await tester.tap(find.byIcon(Icons.format_bold));
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('применяет форматирование курсивом', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test');
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.format_italic));
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('применяет заголовок', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test');
|
||||
await tester.pump();
|
||||
|
||||
// Находим кнопку заголовка (текст 'H')
|
||||
final headingButton = find.text('H');
|
||||
await tester.tap(headingButton);
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('применяет нумерованный список', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test');
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.format_list_numbered));
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('применяет маркированный список', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test');
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.format_list_bulleted));
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('применяет ссылку', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test');
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.link));
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('применяет горизонтальную линию', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, 'Test');
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(textField);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.horizontal_rule));
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, contains('---'));
|
||||
});
|
||||
|
||||
testWidgets('переключает режим preview', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '# Test',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
// Изначально должен быть режим редактирования
|
||||
expect(find.byType(TextField), findsOneWidget);
|
||||
|
||||
// Нажимаем кнопку preview
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Должен быть режим preview
|
||||
expect(find.byType(TextField), findsNothing);
|
||||
|
||||
// Нажимаем кнопку редактирования
|
||||
await tester.tap(find.byIcon(Icons.edit));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Должен вернуться режим редактирования
|
||||
expect(find.byType(TextField), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('вызывает onToolbarVisibilityChanged при изменении видимости', (WidgetTester tester) async {
|
||||
bool? toolbarVisible;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
showInlineToolbar: true,
|
||||
onToolbarVisibilityChanged: (visible) => toolbarVisible = visible,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(toolbarVisible, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('удаляет форматирование жирным если уже применено', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '**Test**',
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.tap(textField);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.format_bold));
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает ссылку в формате markdown', (WidgetTester tester) async {
|
||||
String? changedValue;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '[Link](url)',
|
||||
onChanged: (value) => changedValue = value,
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
final textField = find.byType(TextField);
|
||||
await tester.tap(textField);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byIcon(Icons.link));
|
||||
await tester.pump();
|
||||
|
||||
expect(changedValue, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('отображает preview с заголовком', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '# Heading',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Заголовок рендерится через Text виджет
|
||||
expect(find.textContaining('Heading'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('отображает preview с маркированным списком', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '- Item 1\n- Item 2',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Проверяем, что preview отображается (RichText виджеты должны быть)
|
||||
expect(find.byType(RichText), findsWidgets);
|
||||
// Проверяем наличие текста через RichText
|
||||
final richTexts = find.byType(RichText);
|
||||
expect(richTexts, findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('отображает preview с нумерованным списком', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '1. First\n2. Second',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Проверяем, что preview отображается (RichText виджеты должны быть)
|
||||
expect(find.byType(RichText), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('отображает preview с горизонтальной линией', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '---',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(Divider), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает hasError параметр', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
hasError: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(MarkdownEditor), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает minLines и maxLines', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
minLines: 2,
|
||||
maxLines: 5,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(MarkdownEditor), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает heightPreview', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
heightPreview: 200,
|
||||
initialText: 'Test',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(MarkdownEditor), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает пустой текст в preview', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
hintText: 'Enter text',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Enter text'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает многострочный текст с форматированием', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '# Title\n\n**Bold** text\n\n- List item',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Проверяем, что preview отображается (должны быть Text и RichText виджеты)
|
||||
expect(find.byType(Text), findsWidgets);
|
||||
expect(find.byType(RichText), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает встроенное форматирование в тексте', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: 'This is **bold** and this is *italic*',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Проверяем, что preview отображается (RichText должен быть)
|
||||
expect(find.byType(RichText), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает ссылки в тексте', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: 'Check [this link](https://example.com)',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Проверяем, что preview отображается (RichText должен быть)
|
||||
expect(find.byType(RichText), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает заголовки разных уровней', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: '# H1\n## H2\n### H3',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('H1'), findsOneWidget);
|
||||
expect(find.text('H2'), findsOneWidget);
|
||||
expect(find.text('H3'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('обрабатывает пустые строки в preview', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: MarkdownEditor(
|
||||
initialText: 'Line 1\n\nLine 2',
|
||||
showInlineToolbar: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.visibility));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Проверяем, что preview отображается (RichText должен быть)
|
||||
expect(find.byType(RichText), findsWidgets);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,3 +622,5 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
import 'package:eatvio_app/core/comments/presentation/comments_actions_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() {
|
||||
group('CommentsActionsBottomSheet', () {
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
final router = GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => Scaffold(body: child),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/modal',
|
||||
builder: (context, state) => Scaffold(body: child),
|
||||
),
|
||||
],
|
||||
);
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
@@ -17,7 +32,7 @@ void main() {
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Scaffold(body: child),
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,5 +129,298 @@ void main() {
|
||||
|
||||
expect(find.byType(CommentsActionsBottomSheet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows edit mode when edit button is tapped', (WidgetTester tester) async {
|
||||
final widget = CommentsActionsBottomSheet(
|
||||
isMyComment: true,
|
||||
reply: null,
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'Original text',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find edit button and tap it
|
||||
final editButton = find.text('Редактировать');
|
||||
if (editButton.evaluate().isNotEmpty) {
|
||||
await tester.tap(editButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should show text field for editing
|
||||
expect(find.byType(CustomTextFormField), findsOneWidget);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('calls edit callback when send button is tapped', (WidgetTester tester) async {
|
||||
String? editedText;
|
||||
final widget = CommentsActionsBottomSheet(
|
||||
isMyComment: true,
|
||||
reply: null,
|
||||
copy: () {},
|
||||
edit: (text) {
|
||||
editedText = text;
|
||||
},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'Original text',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap edit button to enter edit mode
|
||||
final editButton = find.text('Редактировать');
|
||||
if (editButton.evaluate().isNotEmpty) {
|
||||
await tester.tap(editButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Enter new text
|
||||
final textField = find.byType(CustomTextFormField);
|
||||
await tester.enterText(textField, 'Edited text');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap send button - callback вызывается ДО pop()
|
||||
final sendButton = find.byIcon(Icons.send_rounded);
|
||||
if (sendButton.evaluate().isNotEmpty) {
|
||||
// Настраиваем FlutterError.onError для перехвата ошибки от GoRouter.pop()
|
||||
final originalOnError = FlutterError.onError;
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
// Игнорируем ошибку "There is nothing to pop" от GoRouter
|
||||
if (details.exception.toString().contains('nothing to pop')) {
|
||||
return;
|
||||
}
|
||||
originalOnError?.call(details);
|
||||
};
|
||||
|
||||
try {
|
||||
await tester.tap(sendButton);
|
||||
await tester.pump();
|
||||
} finally {
|
||||
// Восстанавливаем оригинальный обработчик
|
||||
FlutterError.onError = originalOnError;
|
||||
}
|
||||
|
||||
// Callback вызывается синхронно ДО pop(), проверяем его
|
||||
expect(editedText, equals('Edited text'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('shows reply action when reply callback is provided', (WidgetTester tester) async {
|
||||
final widget = CommentsActionsBottomSheet(
|
||||
isMyComment: false,
|
||||
reply: () {},
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'Test comment',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(CommentsActionsBottomSheet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows delete action for my comment', (WidgetTester tester) async {
|
||||
final widget = CommentsActionsBottomSheet(
|
||||
isMyComment: true,
|
||||
reply: null,
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'My comment',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(CommentsActionsBottomSheet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows complain action for other comment', (WidgetTester tester) async {
|
||||
final widget = CommentsActionsBottomSheet(
|
||||
isMyComment: false,
|
||||
reply: null,
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'Other comment',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(CommentsActionsBottomSheet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows reply action for my comment when reply is provided', (WidgetTester tester) async {
|
||||
final widget = CommentsActionsBottomSheet(
|
||||
isMyComment: true,
|
||||
reply: () {},
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'My comment',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(CommentsActionsBottomSheet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('send button is disabled when text is too short', (WidgetTester tester) async {
|
||||
final widget = CommentsActionsBottomSheet(
|
||||
isMyComment: true,
|
||||
reply: null,
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'Original',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap edit button
|
||||
final editButton = find.text('Редактировать');
|
||||
if (editButton.evaluate().isNotEmpty) {
|
||||
await tester.tap(editButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Enter short text
|
||||
final textField = find.byType(CustomTextFormField);
|
||||
await tester.enterText(textField, 'A');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Send button should be disabled (gray)
|
||||
expect(find.byType(CustomTextFormField), findsOneWidget);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('initializes with comment text in controller', (WidgetTester tester) async {
|
||||
final widget = CommentsActionsBottomSheet(
|
||||
isMyComment: true,
|
||||
reply: null,
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'Initial comment text',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap edit button
|
||||
final editButton = find.text('Редактировать');
|
||||
if (editButton.evaluate().isNotEmpty) {
|
||||
await tester.tap(editButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Text field should contain initial text
|
||||
final textField = find.byType(CustomTextFormField);
|
||||
expect(textField, findsOneWidget);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group('showCommentsActionsBottomSheet', () {
|
||||
Widget createTestWidget(Widget child) {
|
||||
final router = GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => Scaffold(body: child),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/modal',
|
||||
builder: (context, state) => Scaffold(body: child),
|
||||
),
|
||||
],
|
||||
);
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('shows modal bottom sheet', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(createTestWidget(
|
||||
Builder(
|
||||
builder: (context) => ElevatedButton(
|
||||
onPressed: () {
|
||||
showCommentsActionsBottomSheet(
|
||||
context,
|
||||
reply: null,
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'Test comment',
|
||||
isMyComment: false,
|
||||
);
|
||||
},
|
||||
child: const Text('Show'),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap button to show bottom sheet
|
||||
await tester.tap(find.text('Show'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Bottom sheet should be shown
|
||||
expect(find.byType(CommentsActionsBottomSheet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('calls callbacks when actions are tapped', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(createTestWidget(
|
||||
Builder(
|
||||
builder: (context) => ElevatedButton(
|
||||
onPressed: () {
|
||||
showCommentsActionsBottomSheet(
|
||||
context,
|
||||
reply: null,
|
||||
copy: () {},
|
||||
edit: (_) {},
|
||||
delete: () {},
|
||||
complain: () {},
|
||||
commentText: 'Test comment',
|
||||
isMyComment: false,
|
||||
);
|
||||
},
|
||||
child: const Text('Show'),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Show'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(CommentsActionsBottomSheet), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,3 +131,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -66,3 +66,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -80,3 +80,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -29,3 +29,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -55,3 +55,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,3 +25,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,105 @@ void main() {
|
||||
final authRoute = routes.first as GoRoute;
|
||||
expect(authRoute.routes, isNotEmpty);
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут registrationScreen', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final authRoute = routes.first as GoRoute;
|
||||
final registrationRoute = authRoute.routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == 'registration-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(registrationRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут registrationScreenV2', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final authRoute = routes.first as GoRoute;
|
||||
final registrationV2Route = authRoute.routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == 'registration-screen-v2',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(registrationV2Route, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут forgetPasswordScreen', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final authRoute = routes.first as GoRoute;
|
||||
final forgetPasswordRoute = authRoute.routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == 'forget-password-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(forgetPasswordRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут scanQRCodeScreen', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final authRoute = routes.first as GoRoute;
|
||||
final scanQRRoute = authRoute.routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == 'scan-qr-code-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(scanQRRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут verifyScreen', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final verifyRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/verify-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(verifyRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут verifyEmailScreen', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final verifyEmailRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/verify-email-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(verifyEmailRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут registerViaSocialScreen', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final registerViaSocialRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/register-via-social-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(registerViaSocialRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('authScreen имеет redirect функцию', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final authRoute = routes.first as GoRoute;
|
||||
expect(authRoute.redirect, isNotNull);
|
||||
});
|
||||
|
||||
test('forgetPasswordScreen builder обрабатывает extra данные', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final authRoute = routes.first as GoRoute;
|
||||
final forgetPasswordRoute = authRoute.routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == 'forget-password-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
) as GoRoute;
|
||||
expect(forgetPasswordRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('verifyScreen builder обрабатывает extra данные', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final verifyRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/verify-screen',
|
||||
) as GoRoute;
|
||||
expect(verifyRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('registerViaSocialScreen builder обрабатывает extra данные', () {
|
||||
final routes = AuthRoutes.createRoutes();
|
||||
final registerViaSocialRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/register-via-social-screen',
|
||||
) as GoRoute;
|
||||
expect(registerViaSocialRoute.builder, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,3 +131,5 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,114 @@ void main() {
|
||||
);
|
||||
expect(recipeRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут aboutDietScreen', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final dietRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/about-diet-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(dietRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут blogPostCreateScreen', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final blogPostCreateRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/blog-post-create-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(blogPostCreateRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('createRoutes содержит маршрут blogPostScreen', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final blogPostRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/blog-post-screen',
|
||||
orElse: () => throw Exception('Route not found'),
|
||||
);
|
||||
expect(blogPostRoute, isA<GoRoute>());
|
||||
});
|
||||
|
||||
test('aboutPlanScreen builder обрабатывает slug из query параметров', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final planRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/about-plan-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(planRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('aboutPlanScreen builder обрабатывает extra данные', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final planRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/about-plan-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(planRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('aboutRecipesScreen builder обрабатывает slug из query параметров', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final recipeRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/about-recipes-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(recipeRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('aboutRecipesScreen builder обрабатывает extra данные', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final recipeRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/about-recipes-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(recipeRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('aboutDietScreen builder обрабатывает slug из query параметров', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final dietRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/about-diet-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(dietRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('aboutDietScreen builder обрабатывает extra данные', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final dietRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/about-diet-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(dietRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('blogPostCreateScreen builder обрабатывает extra данные', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final blogPostCreateRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/blog-post-create-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(blogPostCreateRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('blogPostScreen builder обрабатывает id из query параметров', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final blogPostRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/blog-post-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(blogPostRoute.builder, isNotNull);
|
||||
});
|
||||
|
||||
test('blogPostScreen builder обрабатывает extra данные', () {
|
||||
final routes = ContentRoutes.createRoutes();
|
||||
final blogPostRoute = routes.firstWhere(
|
||||
(route) => route is GoRoute && route.path == '/blog-post-screen',
|
||||
) as GoRoute;
|
||||
|
||||
expect(blogPostRoute.builder, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,3 +143,5 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,3 +22,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -26,3 +26,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -105,3 +105,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -29,3 +29,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -204,4 +204,22 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
});
|
||||
|
||||
group('ThemeHelper edge cases', () {
|
||||
test('_getThemeColors throws exception for unsupported theme', () {
|
||||
// This tests the exception path, but since _getThemeColors is private
|
||||
// and _appTheme is hardcoded to 'primary', we can't easily test the exception
|
||||
// without reflection or making the method public
|
||||
// The current implementation always uses 'primary' theme
|
||||
final helper = ThemeHelper();
|
||||
expect(helper.themeColor(), isA<PrimaryColors>());
|
||||
});
|
||||
|
||||
test('_getThemeData throws exception for unsupported theme', () {
|
||||
// Similar to above, this tests the exception path
|
||||
// The current implementation always uses 'primary' theme
|
||||
final helper = ThemeHelper();
|
||||
expect(helper.themeData(), isA<ThemeData>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,5 +92,159 @@ void main() {
|
||||
|
||||
verify(() => mockRepository.viewContent(viewModel: any(named: 'viewModel'))).called(greaterThan(0));
|
||||
});
|
||||
|
||||
test('handleVisibility - работает с diets типом контента', () async {
|
||||
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.diets,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 1100));
|
||||
|
||||
expect(cubit.dietsIds.contains(1), true);
|
||||
});
|
||||
|
||||
test('handleVisibility - работает с recipes типом контента', () async {
|
||||
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.recipes,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 1100));
|
||||
|
||||
expect(cubit.recipesIds.contains(1), true);
|
||||
});
|
||||
|
||||
test('handleVisibility - не добавляет id если он уже отправлен', () async {
|
||||
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
// Сначала добавляем и отправляем id
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.posts,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 1100));
|
||||
await Future.delayed(const Duration(milliseconds: 4100)); // Ждем отправки
|
||||
|
||||
// Пытаемся добавить снова
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.posts,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 1100));
|
||||
|
||||
// ID не должен быть добавлен повторно
|
||||
expect(cubit.postsIds.contains(1), false);
|
||||
});
|
||||
|
||||
test('handleVisibility - не добавляет id если он уже в ids', () async {
|
||||
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
cubit.postsIds.add(1);
|
||||
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.posts,
|
||||
);
|
||||
|
||||
// ID уже был в списке, не должен быть добавлен повторно
|
||||
expect(cubit.postsIds.length, 1);
|
||||
|
||||
// Закрываем cubit, чтобы избежать ошибок при tearDown
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
test('handleVisibility - не создает новый таймер если он уже существует', () async {
|
||||
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
// Первый вызов создает таймер
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.posts,
|
||||
);
|
||||
|
||||
// Второй вызов не должен создавать новый таймер
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.posts,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 1100));
|
||||
|
||||
expect(cubit.postsIds.contains(1), true);
|
||||
});
|
||||
|
||||
test('handleVisibility - отменяет таймер при visibleFraction < 0.5', () async {
|
||||
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
// Сначала видимый
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.posts,
|
||||
);
|
||||
|
||||
// Затем скрываем
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.3,
|
||||
id: 1,
|
||||
type: ViewableContentType.posts,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 1100));
|
||||
|
||||
// ID не должен быть добавлен, так как таймер был отменен
|
||||
expect(cubit.postsIds.contains(1), false);
|
||||
});
|
||||
|
||||
test('_sendViews отправляет просмотры для всех типов контента', () async {
|
||||
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
cubit.postsIds.add(1);
|
||||
cubit.plansIds.add(2);
|
||||
cubit.dietsIds.add(3);
|
||||
cubit.recipesIds.add(4);
|
||||
|
||||
await cubit.close();
|
||||
|
||||
// Проверяем, что были вызваны для всех типов
|
||||
verify(() => mockRepository.viewContent(viewModel: any(named: 'viewModel'))).called(greaterThanOrEqualTo(4));
|
||||
});
|
||||
|
||||
test('handleVisibility - debounce отправляет просмотры через 4 секунды', () async {
|
||||
when(() => mockRepository.viewContent(viewModel: any(named: 'viewModel')))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
cubit.handleVisibility(
|
||||
visibleFraction: 0.6,
|
||||
id: 1,
|
||||
type: ViewableContentType.posts,
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 1100)); // Ждем добавления в ids
|
||||
await Future.delayed(const Duration(milliseconds: 4100)); // Ждем debounce отправки
|
||||
|
||||
verify(() => mockRepository.viewContent(viewModel: any(named: 'viewModel'))).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,3 +23,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,3 +59,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -111,3 +111,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,3 +48,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -130,3 +130,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -91,3 +91,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -159,3 +159,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -52,3 +52,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -37,3 +37,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -91,3 +91,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -71,3 +71,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -60,3 +60,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:vk_id/vk_id.dart';
|
||||
|
||||
void main() {
|
||||
group('AuthResultEntity', () {
|
||||
@@ -100,5 +101,21 @@ void main() {
|
||||
expect(GenderEnum.female, isNotNull);
|
||||
expect(GenderEnum.notSet, isNotNull);
|
||||
});
|
||||
|
||||
test('fromVkGender returns correct enum for VkSex.male', () {
|
||||
expect(GenderEnum.fromVkGender(VkSex.male), GenderEnum.male);
|
||||
});
|
||||
|
||||
test('fromVkGender returns correct enum for VkSex.female', () {
|
||||
expect(GenderEnum.fromVkGender(VkSex.female), GenderEnum.female);
|
||||
});
|
||||
|
||||
test('fromVkGender returns correct enum for VkSex.notStated', () {
|
||||
expect(GenderEnum.fromVkGender(VkSex.notStated), GenderEnum.notSet);
|
||||
});
|
||||
|
||||
test('fromVkGender returns null for null VkSex', () {
|
||||
expect(GenderEnum.fromVkGender(null), null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,6 +123,200 @@ void main() {
|
||||
// Состояние должно быть error из-за ошибки загрузки профиля
|
||||
expect(cubit!.state, isA<ProfileAvatarStateError>());
|
||||
});
|
||||
|
||||
test('getMyProfile - обработка ошибки при получении деталей профиля', () async {
|
||||
cubit = ProfileAvatarCubit(profileRepository: mockRepository);
|
||||
|
||||
const testProfile = ProfileModel(
|
||||
id: 1,
|
||||
slug: 'test-user',
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: 'https://example.com/icon.jpg',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
when(mockRepository.getMyProfile())
|
||||
.thenAnswer((_) async => testProfile);
|
||||
when(mockRepository.getMyProfileDetails())
|
||||
.thenThrow(Exception('Ошибка загрузки деталей'));
|
||||
|
||||
await cubit!.getMyProfile();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Если детали не загрузились, выбрасывается исключение и состояние становится error
|
||||
expect(cubit!.state, isA<ProfileAvatarStateError>());
|
||||
});
|
||||
|
||||
test('getMyProfile - обработка ошибки при получении профиля', () async {
|
||||
cubit = ProfileAvatarCubit(profileRepository: mockRepository);
|
||||
|
||||
when(mockRepository.getMyProfile())
|
||||
.thenThrow(Exception('Ошибка загрузки профиля'));
|
||||
|
||||
await cubit!.getMyProfile();
|
||||
|
||||
// Состояние должно быть error
|
||||
expect(cubit!.state, isA<ProfileAvatarStateError>());
|
||||
final errorState = cubit!.state as ProfileAvatarStateError;
|
||||
expect(errorState.errorMessage, contains('Ошибка загрузки профиля'));
|
||||
});
|
||||
|
||||
test('getMyProfile - успешная загрузка с женским полом', () async {
|
||||
cubit = ProfileAvatarCubit(profileRepository: mockRepository);
|
||||
|
||||
const testProfile = ProfileModel(
|
||||
id: 1,
|
||||
slug: 'test-user',
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: 'https://example.com/icon.jpg',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
const testProfileDetails = ProfileDetailsModel(
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'female',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 60.0,
|
||||
height: 165,
|
||||
chestCircumference: 90,
|
||||
waistCircumference: 70,
|
||||
hipCircumference: 95,
|
||||
bloodPressureUp: 110,
|
||||
bloodPressureDown: 70,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: 'No allergies',
|
||||
aboutTakingPils: 'No pills',
|
||||
aboutSportHobbies: 'Yoga',
|
||||
favoriteFoods: 'Vegetables',
|
||||
unfavoriteFoods: 'Meat',
|
||||
additionalInfo: 'Additional info',
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
activityCoefficient: 1.2,
|
||||
);
|
||||
|
||||
when(mockRepository.getMyProfile())
|
||||
.thenAnswer((_) async => testProfile);
|
||||
when(mockRepository.getMyProfileDetails())
|
||||
.thenAnswer((_) async => testProfileDetails);
|
||||
|
||||
ProfileAvatarState? loadedState;
|
||||
final subscription = cubit!.stream.listen((state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
loadedState = state;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await cubit!.getMyProfile();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
} catch (e) {
|
||||
// Игнорируем ошибки плагинов
|
||||
} finally {
|
||||
await subscription.cancel();
|
||||
}
|
||||
|
||||
expect(loadedState, isNotNull);
|
||||
});
|
||||
|
||||
test('getMyProfile - успешная загрузка с различными форматами даты рождения', () async {
|
||||
cubit = ProfileAvatarCubit(profileRepository: mockRepository);
|
||||
|
||||
const testProfile = ProfileModel(
|
||||
id: 1,
|
||||
slug: 'test-user',
|
||||
name: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phoneConfirmationState: 'confirmed',
|
||||
isPublicProfile: true,
|
||||
isBlockedByMe: false,
|
||||
isBlockedByAuthor: false,
|
||||
isBlockedByUser: false,
|
||||
isEntitySubscribedByMe: false,
|
||||
profilePhotoUrl: 'https://example.com/icon.jpg',
|
||||
roles: [],
|
||||
badges: [],
|
||||
subscriptions: [],
|
||||
subscribers: [],
|
||||
);
|
||||
|
||||
// Тест с датой рождения в будущем (для проверки расчета возраста)
|
||||
const testProfileDetails = ProfileDetailsModel(
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
birthdate: '2000-06-15',
|
||||
gender: 'male',
|
||||
country: 'Russia',
|
||||
city: 'Moscow',
|
||||
lang: 'ru',
|
||||
weight: 70.0,
|
||||
height: 175,
|
||||
chestCircumference: 100,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 90,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: false,
|
||||
hasHyper: false,
|
||||
aboutAlergy: 'No allergies',
|
||||
aboutTakingPils: 'No pills',
|
||||
aboutSportHobbies: 'Running',
|
||||
favoriteFoods: 'Fruits',
|
||||
unfavoriteFoods: 'Fast food',
|
||||
additionalInfo: 'Additional info',
|
||||
isMoodFillable: true,
|
||||
isBedtimeFillable: true,
|
||||
activityCoefficient: 1.5,
|
||||
);
|
||||
|
||||
when(mockRepository.getMyProfile())
|
||||
.thenAnswer((_) async => testProfile);
|
||||
when(mockRepository.getMyProfileDetails())
|
||||
.thenAnswer((_) async => testProfileDetails);
|
||||
|
||||
ProfileAvatarState? loadedState;
|
||||
final subscription = cubit!.stream.listen((state) {
|
||||
if (state is ProfileAvatarStateLoaded) {
|
||||
loadedState = state;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await cubit!.getMyProfile();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
} catch (e) {
|
||||
// Игнорируем ошибки плагинов
|
||||
} finally {
|
||||
await subscription.cancel();
|
||||
}
|
||||
|
||||
expect(loadedState, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
void main() {
|
||||
group('RatingHistoryWidget', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display loader when isLoading is true', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = RatingHistoryWidget(isLoading: true, ratingHistory: []);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(Loader), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display rating history items when isLoading is false', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final ratingHistory = [
|
||||
RatingModel(
|
||||
type: RatingEnum.addedRecipe,
|
||||
amount: 10,
|
||||
date: DateTime(2024, 1, 1),
|
||||
),
|
||||
RatingModel(
|
||||
type: RatingEnum.addedBlogPost,
|
||||
amount: 5,
|
||||
date: DateTime(2024, 1, 2),
|
||||
),
|
||||
];
|
||||
final widget = RatingHistoryWidget(isLoading: false, ratingHistory: ratingHistory);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(Loader), findsNothing);
|
||||
expect(find.byType(ListView), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display empty list when ratingHistory is empty', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = RatingHistoryWidget(isLoading: false, ratingHistory: []);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(Loader), findsNothing);
|
||||
expect(find.byType(ListView), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display all rating types correctly', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final ratingHistory = [
|
||||
RatingModel(type: RatingEnum.addedRecipe, amount: 1, date: DateTime.now()),
|
||||
RatingModel(type: RatingEnum.addedPublicDiet, amount: 2, date: DateTime.now()),
|
||||
RatingModel(type: RatingEnum.addedBlogPost, amount: 3, date: DateTime.now()),
|
||||
RatingModel(type: RatingEnum.invitedUser, amount: 4, date: DateTime.now()),
|
||||
RatingModel(type: RatingEnum.hasMedicalDegree, amount: 5, date: DateTime.now()),
|
||||
RatingModel(type: RatingEnum.consulted, amount: 6, date: DateTime.now()),
|
||||
RatingModel(type: RatingEnum.addedGlossary, amount: 7, date: DateTime.now()),
|
||||
];
|
||||
final widget = RatingHistoryWidget(isLoading: false, ratingHistory: ratingHistory);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(ListView), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
void main() {
|
||||
group('StatisticWidget', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display loader when isLoading is true', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = StatisticWidget(isLoading: true, statistics: null);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(Loader), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display statistics when isLoading is false and statistics provided', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final statistics = StatisticsModel(
|
||||
countReferal: 10,
|
||||
blogPostsCount: 5,
|
||||
recipesCount: 3,
|
||||
dietsCount: 2,
|
||||
consultationsCount: 8,
|
||||
qwe: 1,
|
||||
);
|
||||
final widget = StatisticWidget(isLoading: false, statistics: statistics);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(Loader), findsNothing);
|
||||
expect(find.text('10'), findsOneWidget);
|
||||
expect(find.text('5'), findsOneWidget);
|
||||
expect(find.text('3'), findsOneWidget);
|
||||
expect(find.text('2'), findsOneWidget);
|
||||
expect(find.text('8'), findsOneWidget);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display zeros when statistics is null', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = StatisticWidget(isLoading: false, statistics: null);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(Loader), findsNothing);
|
||||
expect(find.text('0'), findsNWidgets(6));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
|
||||
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
|
||||
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
|
||||
@@ -6,89 +8,281 @@ import 'package:eatvio_app/features/finances_and_consultations_archive/models/us
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../../fixtures/fixture_reader.dart';
|
||||
import 'consultation_service_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<Dio>()])
|
||||
@GenerateMocks([Dio])
|
||||
void main() {
|
||||
group('ConsultationService', () {
|
||||
late ConsultationService consultationService;
|
||||
late ConsultationService service;
|
||||
late MockDio mockDio;
|
||||
|
||||
setUp(() {
|
||||
mockDio = MockDio();
|
||||
consultationService = ConsultationService(mockDio);
|
||||
service = ConsultationService(mockDio);
|
||||
});
|
||||
|
||||
test('getConsultations - возвращает список консультаций', () async {
|
||||
final json = FixtureReader.readAsMap('consultations/consultation_pagination_response.json');
|
||||
final response = Response(
|
||||
data: json,
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(path: '/api/consultations'),
|
||||
test('getConsultations - успешное получение консультаций', () async {
|
||||
final mockData = {
|
||||
'data': [],
|
||||
'links': {},
|
||||
'meta': {'current_page': 1, 'last_page': 1, 'total': 0, 'per_page': 10},
|
||||
};
|
||||
|
||||
when(mockDio.get('/api/consultations', queryParameters: anyNamed('queryParameters'))).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/consultations'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
when(mockDio.get(any, queryParameters: anyNamed('queryParameters')))
|
||||
.thenAnswer((_) async => response);
|
||||
|
||||
final result = await consultationService.getConsultations();
|
||||
final result = await service.getConsultations();
|
||||
|
||||
expect(result, isA<ConsultationPaginationModel>());
|
||||
expect(result.data.length, 1);
|
||||
verify(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).called(1);
|
||||
verify(
|
||||
mockDio.get(
|
||||
'/api/consultations',
|
||||
queryParameters: {
|
||||
'filters[last_state][\$notIn][]': const [
|
||||
'initied',
|
||||
'in_coordination',
|
||||
'start_date_has_been_set',
|
||||
'pending_payment',
|
||||
'canceled_by_the_initiator',
|
||||
'charge_was_collected',
|
||||
],
|
||||
'sort[]': 'created_at:desc',
|
||||
},
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('getStatistics - возвращает статистику', () async {
|
||||
final json = FixtureReader.readAsMap('consultations/statistic_response.json');
|
||||
final response = Response(
|
||||
data: json,
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(path: '/api/consultations/statistics'),
|
||||
test('getConsultations - пробрасывает ошибку', () async {
|
||||
when(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).thenThrow(DioException(requestOptions: RequestOptions(path: '/api/consultations')));
|
||||
|
||||
expect(() => service.getConsultations(), throwsA(isA<DioException>()));
|
||||
});
|
||||
|
||||
test('getFilteredConsultations - успешное получение с фильтрами notInArray', () async {
|
||||
final mockData = {
|
||||
'data': [],
|
||||
'links': {},
|
||||
'meta': {'current_page': 1, 'last_page': 1, 'total': 0, 'per_page': 10},
|
||||
};
|
||||
|
||||
when(mockDio.get('/api/consultations', queryParameters: anyNamed('queryParameters'))).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/consultations'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
when(mockDio.get(any))
|
||||
.thenAnswer((_) async => response);
|
||||
final result = await service.getFilteredConsultations(stateComparison: FiltersComparisonEnum.notInArray);
|
||||
|
||||
final result = await consultationService.getStatistics();
|
||||
expect(result, isA<ConsultationPaginationModel>());
|
||||
verify(
|
||||
mockDio.get(
|
||||
'/api/consultations',
|
||||
queryParameters: {
|
||||
'filters[last_state][\$notIn][]': const [
|
||||
'initied',
|
||||
'in_coordination',
|
||||
'start_date_has_been_set',
|
||||
'pending_payment',
|
||||
'canceled_by_the_initiator',
|
||||
'charge_was_collected',
|
||||
],
|
||||
'sort[]': 'created_at:desc',
|
||||
},
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('getFilteredConsultations - успешное получение с фильтром state', () async {
|
||||
final mockData = {
|
||||
'data': [],
|
||||
'links': {},
|
||||
'meta': {'current_page': 1, 'last_page': 1, 'total': 0, 'per_page': 10},
|
||||
};
|
||||
|
||||
when(mockDio.get('/api/consultations', queryParameters: anyNamed('queryParameters'))).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/consultations'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
final result = await service.getFilteredConsultations(state: 'done', stateComparison: FiltersComparisonEnum.equal);
|
||||
|
||||
expect(result, isA<ConsultationPaginationModel>());
|
||||
verify(mockDio.get('/api/consultations', queryParameters: {'filters[last_state][\$eq]': 'done', 'sort[]': 'created_at:desc'})).called(1);
|
||||
});
|
||||
|
||||
test('getFilteredConsultations - успешное получение с фильтром date', () async {
|
||||
final mockData = {
|
||||
'data': [],
|
||||
'links': {},
|
||||
'meta': {'current_page': 1, 'last_page': 1, 'total': 0, 'per_page': 10},
|
||||
};
|
||||
|
||||
when(mockDio.get('/api/consultations', queryParameters: anyNamed('queryParameters'))).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/consultations'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
final result = await service.getFilteredConsultations(date: '2024-01-01', dateComparison: FiltersComparisonEnum.greaterThan);
|
||||
|
||||
expect(result, isA<ConsultationPaginationModel>());
|
||||
verify(mockDio.get('/api/consultations', queryParameters: {'filters[start_date][\$gt]': '2024-01-01', 'sort[]': 'created_at:desc'})).called(1);
|
||||
});
|
||||
|
||||
test('getFilteredConsultations - пробрасывает ошибку', () async {
|
||||
when(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).thenThrow(DioException(requestOptions: RequestOptions(path: '/api/consultations')));
|
||||
|
||||
expect(() => service.getFilteredConsultations(), throwsA(isA<DioException>()));
|
||||
});
|
||||
|
||||
test('getStatistics - успешное получение статистики', () async {
|
||||
final mockData = {'total': 10, 'in_progress': 5, 'finished': 3, 'canceled': 2};
|
||||
|
||||
when(mockDio.get('/api/consultations/statistics')).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/consultations/statistics'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
final result = await service.getStatistics();
|
||||
|
||||
expect(result, isA<StatisticModel>());
|
||||
expect(result.total, 10);
|
||||
verify(mockDio.get(any)).called(1);
|
||||
expect(result.inProgress, 5);
|
||||
expect(result.finished, 3);
|
||||
expect(result.canceled, 2);
|
||||
verify(mockDio.get('/api/consultations/statistics')).called(1);
|
||||
});
|
||||
|
||||
test('getConsultantFinances - возвращает финансы консультанта', () async {
|
||||
final response = Response(
|
||||
data: {'data': []},
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(path: '/api/finances/my_finances'),
|
||||
test('getStatistics - пробрасывает ошибку', () async {
|
||||
when(mockDio.get('/api/consultations/statistics')).thenThrow(DioException(requestOptions: RequestOptions(path: '/api/consultations/statistics')));
|
||||
|
||||
expect(() => service.getStatistics(), throwsA(isA<DioException>()));
|
||||
});
|
||||
|
||||
test('getConsultantFinances - успешное получение финансов', () async {
|
||||
final mockData = {
|
||||
'data': [
|
||||
{
|
||||
'start_date': '2024-01-01T00:00:00Z',
|
||||
'bank_id': 'bank-1',
|
||||
'title': 'Consultation',
|
||||
'clientName': 'Client Name',
|
||||
'duration': 60,
|
||||
'payment_price': 100000,
|
||||
'payment_status': 'COMPLETED',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
when(mockDio.get('/api/finances/my_finances', queryParameters: anyNamed('queryParameters'))).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/finances/my_finances'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
when(mockDio.get(any, queryParameters: anyNamed('queryParameters')))
|
||||
.thenAnswer((_) async => response);
|
||||
|
||||
final result = await consultationService.getConsultantFinances(null, null);
|
||||
final result = await service.getConsultantFinances(null, null);
|
||||
|
||||
expect(result, isA<List<ConsultantFinanceModel>>());
|
||||
verify(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).called(1);
|
||||
expect(result.length, 1);
|
||||
verify(mockDio.get('/api/finances/my_finances', queryParameters: {'sort[]': 'start_date:desc'})).called(1);
|
||||
});
|
||||
|
||||
test('getUserPurchases - возвращает покупки пользователя', () async {
|
||||
final response = Response(
|
||||
data: {'data': []},
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(path: '/api/finances/my_purchases'),
|
||||
test('getConsultantFinances - успешное получение с фильтром date', () async {
|
||||
final mockData = {'data': []};
|
||||
|
||||
when(mockDio.get('/api/finances/my_finances', queryParameters: anyNamed('queryParameters'))).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/finances/my_finances'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
when(mockDio.get(any, queryParameters: anyNamed('queryParameters')))
|
||||
.thenAnswer((_) async => response);
|
||||
final result = await service.getConsultantFinances('2024-01-01', FiltersComparisonEnum.equal);
|
||||
|
||||
final result = await consultationService.getUserPurchases(null, null);
|
||||
expect(result, isA<List<ConsultantFinanceModel>>());
|
||||
verify(mockDio.get('/api/finances/my_finances', queryParameters: {'filters[start_date][\$eq]': '2024-01-01', 'sort[]': 'start_date:desc'})).called(1);
|
||||
});
|
||||
|
||||
test('getConsultantFinances - пробрасывает ошибку', () async {
|
||||
when(
|
||||
mockDio.get(any, queryParameters: anyNamed('queryParameters')),
|
||||
).thenThrow(DioException(requestOptions: RequestOptions(path: '/api/finances/my_finances')));
|
||||
|
||||
expect(() => service.getConsultantFinances(null, null), throwsA(isA<DioException>()));
|
||||
});
|
||||
|
||||
test('getUserPurchases - успешное получение покупок', () async {
|
||||
final mockData = {
|
||||
'data': [
|
||||
{
|
||||
'start_date': '2024-01-01T00:00:00Z',
|
||||
'bank_id': 'bank-1',
|
||||
'title': 'Plan Title',
|
||||
'consultantName': 'Consultant Name',
|
||||
'duration': 30,
|
||||
'price': 1000,
|
||||
'chosen_plan_last_state': 'paid',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
when(mockDio.get('/api/finances/my_purchases', queryParameters: anyNamed('queryParameters'))).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/finances/my_purchases'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
final result = await service.getUserPurchases(null, null);
|
||||
|
||||
expect(result, isA<List<UserPurchasesModel>>());
|
||||
verify(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).called(1);
|
||||
expect(result.length, 1);
|
||||
verify(mockDio.get('/api/finances/my_purchases', queryParameters: {'sort[]': 'start_date:desc'})).called(1);
|
||||
});
|
||||
|
||||
test('getUserPurchases - успешное получение с фильтром date', () async {
|
||||
final mockData = {'data': []};
|
||||
|
||||
when(mockDio.get('/api/finances/my_purchases', queryParameters: anyNamed('queryParameters'))).thenAnswer(
|
||||
(_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/finances/my_purchases'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
),
|
||||
);
|
||||
|
||||
final result = await service.getUserPurchases('2024-01-01', FiltersComparisonEnum.greaterThan);
|
||||
|
||||
expect(result, isA<List<UserPurchasesModel>>());
|
||||
verify(mockDio.get('/api/finances/my_purchases', queryParameters: {'filters[start_date][\$gt]': '2024-01-01', 'sort[]': 'start_date:desc'})).called(1);
|
||||
});
|
||||
|
||||
test('getUserPurchases - пробрасывает ошибку', () async {
|
||||
when(
|
||||
mockDio.get(any, queryParameters: anyNamed('queryParameters')),
|
||||
).thenThrow(DioException(requestOptions: RequestOptions(path: '/api/finances/my_purchases')));
|
||||
|
||||
expect(() => service.getUserPurchases(null, null), throwsA(isA<DioException>()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -66,3 +66,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -75,3 +75,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -53,3 +53,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,3 +48,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -85,3 +85,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -37,3 +37,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/cancel_diet_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'cancel_diet_bottom_sheet_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<AboutDietCubit>()])
|
||||
void main() {
|
||||
group('CancelDietBottomSheet', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child, AboutDietCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: BlocProvider<AboutDietCubit>(
|
||||
create: (_) => cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display cancel diet bottom sheet with default title', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockAboutDietCubit();
|
||||
final widget = CancelDietBottomSheet(
|
||||
aboutDietCubit: mockCubit,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, mockCubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(CancelDietBottomSheet), findsOneWidget);
|
||||
expect(find.byType(CustomOutlinedButton), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display cancel diet bottom sheet with custom title', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockAboutDietCubit();
|
||||
final widget = CancelDietBottomSheet(
|
||||
aboutDietCubit: mockCubit,
|
||||
title: 'Custom title',
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, mockCubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(CancelDietBottomSheet), findsOneWidget);
|
||||
expect(find.text('Custom title'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display cancel diet bottom sheet with diet name', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockAboutDietCubit();
|
||||
final widget = CancelDietBottomSheet(
|
||||
aboutDietCubit: mockCubit,
|
||||
dietName: 'Test Diet',
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, mockCubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(CancelDietBottomSheet), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,3 +103,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/start_diet_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'start_diet_bottom_sheet_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<AboutDietCubit>()])
|
||||
void main() {
|
||||
group('StartDietBottomSheet', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child, AboutDietCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: BlocProvider<AboutDietCubit>(
|
||||
create: (_) => cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display start diet bottom sheet', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockAboutDietCubit();
|
||||
final widget = StartDietBottomSheet(
|
||||
aboutDietCubit: mockCubit,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, mockCubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(StartDietBottomSheet), findsOneWidget);
|
||||
expect(find.byType(CustomOutlinedButton), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -133,3 +133,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -45,3 +45,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -53,3 +53,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,3 +46,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -189,6 +189,225 @@ void main() {
|
||||
|
||||
verify(mockWsManager.newNotificationsStream).called(1);
|
||||
});
|
||||
|
||||
test('loadMoreNotifications - обработка ошибки при загрузке', () async {
|
||||
final notifications = [
|
||||
ChosenPlanStartedToClientNotification(
|
||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||
planName: 'Plan 1',
|
||||
planId: 1,
|
||||
isRead: false,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 1))
|
||||
.thenAnswer((_) async => {
|
||||
'notifications': notifications,
|
||||
'pagination': {'current_page': 1, 'last_page': 2},
|
||||
});
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 2))
|
||||
.thenThrow(Exception('Ошибка загрузки'));
|
||||
|
||||
await cubit.getNotifications();
|
||||
await cubit.loadMoreNotifications();
|
||||
|
||||
final state = cubit.state;
|
||||
expect(state, isA<NotificationsStateLoaded>());
|
||||
if (state is NotificationsStateLoaded) {
|
||||
expect(state.isLoadingMore, false);
|
||||
}
|
||||
});
|
||||
|
||||
test('loadMoreNotifications - не загружает если уже загружается', () async {
|
||||
final notifications = [
|
||||
ChosenPlanStartedToClientNotification(
|
||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||
planName: 'Plan 1',
|
||||
planId: 1,
|
||||
isRead: false,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 1))
|
||||
.thenAnswer((_) async => {
|
||||
'notifications': notifications,
|
||||
'pagination': {'current_page': 1, 'last_page': 2},
|
||||
});
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 2))
|
||||
.thenAnswer((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
return {
|
||||
'notifications': notifications,
|
||||
'pagination': {'current_page': 2, 'last_page': 2},
|
||||
};
|
||||
});
|
||||
|
||||
await cubit.getNotifications();
|
||||
|
||||
// Вызываем loadMoreNotifications дважды быстро
|
||||
final future1 = cubit.loadMoreNotifications();
|
||||
// Небольшая задержка, чтобы первый вызов успел установить isLoadingMore
|
||||
await Future.delayed(const Duration(milliseconds: 10));
|
||||
final future2 = cubit.loadMoreNotifications();
|
||||
|
||||
await future1;
|
||||
await future2;
|
||||
|
||||
// Должен быть вызван только один раз, так как второй вызов должен быть заблокирован isLoadingMore
|
||||
verify(mockNotificationsRepository.getNotifications(page: 2)).called(1);
|
||||
});
|
||||
|
||||
test('markNotificationsAsRead - обработка ошибки', () async {
|
||||
final notifications = [
|
||||
ChosenPlanStartedToClientNotification(
|
||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||
planName: 'Plan 1',
|
||||
planId: 1,
|
||||
isRead: false,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 1))
|
||||
.thenAnswer((_) async => {
|
||||
'notifications': notifications,
|
||||
'pagination': {'current_page': 1, 'last_page': 1},
|
||||
});
|
||||
|
||||
when(mockNotificationsRepository.markNotificationsAsRead())
|
||||
.thenThrow(Exception('Ошибка пометки'));
|
||||
|
||||
await cubit.getNotifications();
|
||||
await cubit.markNotificationsAsRead();
|
||||
|
||||
final state = cubit.state;
|
||||
expect(state, isA<NotificationsStateLoaded>());
|
||||
if (state is NotificationsStateLoaded) {
|
||||
expect(state.isLoading, false);
|
||||
}
|
||||
});
|
||||
|
||||
test('getNotifications - возвращает null от репозитория', () async {
|
||||
when(mockNotificationsRepository.getNotifications(page: 1))
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
await cubit.getNotifications();
|
||||
|
||||
// Состояние должно остаться empty
|
||||
expect(cubit.state, isA<NotificationsStateEmpty>());
|
||||
});
|
||||
|
||||
test('loadMoreNotifications - возвращает null от репозитория', () async {
|
||||
final notifications = [
|
||||
ChosenPlanStartedToClientNotification(
|
||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||
planName: 'Plan 1',
|
||||
planId: 1,
|
||||
isRead: false,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 1))
|
||||
.thenAnswer((_) async => {
|
||||
'notifications': notifications,
|
||||
'pagination': {'current_page': 1, 'last_page': 2},
|
||||
});
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 2))
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
await cubit.getNotifications();
|
||||
await cubit.loadMoreNotifications();
|
||||
|
||||
final state = cubit.state;
|
||||
expect(state, isA<NotificationsStateLoaded>());
|
||||
if (state is NotificationsStateLoaded) {
|
||||
expect(state.isLoadingMore, false);
|
||||
expect(state.notifications.length, 1); // Только первая страница
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('getNotifications - проверяет hasUnreadNotifications', () async {
|
||||
final notifications = [
|
||||
ChosenPlanStartedToClientNotification(
|
||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||
planName: 'Plan 1',
|
||||
planId: 1,
|
||||
isRead: false,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
ChosenPlanStartedToClientNotification(
|
||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||
planName: 'Plan 2',
|
||||
planId: 2,
|
||||
isRead: true,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
final result = {
|
||||
'notifications': notifications,
|
||||
'pagination': {
|
||||
'current_page': 1,
|
||||
'last_page': 1,
|
||||
},
|
||||
};
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 1))
|
||||
.thenAnswer((_) async => result);
|
||||
|
||||
await cubit.getNotifications();
|
||||
|
||||
final state = cubit.state;
|
||||
expect(state, isA<NotificationsStateLoaded>());
|
||||
if (state is NotificationsStateLoaded) {
|
||||
expect(state.hasUnreadNotifications, true); // Есть непрочитанное
|
||||
}
|
||||
});
|
||||
|
||||
test('getNotifications - hasUnreadNotifications false когда все прочитаны', () async {
|
||||
final notifications = [
|
||||
ChosenPlanStartedToClientNotification(
|
||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||
planName: 'Plan 1',
|
||||
planId: 1,
|
||||
isRead: true,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
ChosenPlanStartedToClientNotification(
|
||||
type: NotificationType.chosenPlanStartedToClientNotification,
|
||||
planName: 'Plan 2',
|
||||
planId: 2,
|
||||
isRead: true,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
final result = {
|
||||
'notifications': notifications,
|
||||
'pagination': {
|
||||
'current_page': 1,
|
||||
'last_page': 1,
|
||||
},
|
||||
};
|
||||
|
||||
when(mockNotificationsRepository.getNotifications(page: 1))
|
||||
.thenAnswer((_) async => result);
|
||||
|
||||
await cubit.getNotifications();
|
||||
|
||||
final state = cubit.state;
|
||||
expect(state, isA<NotificationsStateLoaded>());
|
||||
if (state is NotificationsStateLoaded) {
|
||||
expect(state.hasUnreadNotifications, false); // Все прочитаны
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1023,3 +1023,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/collect_charge_banner.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart' show PlanModel, PlanType;
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart' show Author;
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() {
|
||||
group('CollectChargeBanner', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => Portal(
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display collect charge banner with plan name and price', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final chosenPlan = ChosenPlanModel(
|
||||
id: 1,
|
||||
optionType: 'standard',
|
||||
startDate: DateTime.now(),
|
||||
chosenPlanState: ChosenPlanStateEnum.stateDone,
|
||||
chatId: 'chat-1',
|
||||
consultantId: 1,
|
||||
plan: const PlanModel(
|
||||
id: 1,
|
||||
slug: 'test-plan',
|
||||
name: 'Test Plan',
|
||||
image: 'test.jpg',
|
||||
duration: 30,
|
||||
state: 'active',
|
||||
type: PlanType(fit: true, slim: false, behavior: false, wellness: false),
|
||||
details: 'Test details',
|
||||
packetsCount: 1,
|
||||
author: Author(authorSlug: '', authorName: '', authorProfileIcon: '', authorIsMe: false),
|
||||
totalLikes: 0,
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
isFavorite: false,
|
||||
minPriceOfOptions: 100.0,
|
||||
maxPriceOfOptions: 500.0,
|
||||
wasLikedByMe: false,
|
||||
),
|
||||
);
|
||||
const price = 1000.0;
|
||||
final widget = CollectChargeBanner(chosenPlan: chosenPlan, price: price);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(CollectChargeBanner), findsOneWidget);
|
||||
expect(find.byType(CustomOutlinedButton), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should have button that navigates to card settings', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final chosenPlan = ChosenPlanModel(
|
||||
id: 1,
|
||||
optionType: 'standard',
|
||||
startDate: DateTime.now(),
|
||||
chosenPlanState: ChosenPlanStateEnum.stateDone,
|
||||
chatId: 'chat-1',
|
||||
consultantId: 1,
|
||||
plan: const PlanModel(
|
||||
id: 1,
|
||||
slug: 'test-plan',
|
||||
name: 'Test Plan',
|
||||
image: 'test.jpg',
|
||||
duration: 30,
|
||||
state: 'active',
|
||||
type: PlanType(fit: true, slim: false, behavior: false, wellness: false),
|
||||
details: 'Test details',
|
||||
packetsCount: 1,
|
||||
author: Author(authorSlug: '', authorName: '', authorProfileIcon: '', authorIsMe: false),
|
||||
totalLikes: 0,
|
||||
totalViews: 0,
|
||||
totalComments: 0,
|
||||
isFavorite: false,
|
||||
minPriceOfOptions: 100.0,
|
||||
maxPriceOfOptions: 500.0,
|
||||
wasLikedByMe: false,
|
||||
),
|
||||
);
|
||||
const price = 1000.0;
|
||||
final widget = CollectChargeBanner(chosenPlan: chosenPlan, price: price);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert - проверяем, что кнопка существует
|
||||
expect(find.byType(CustomOutlinedButton), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/payment_banner.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'payment_banner_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<OrderInfoCubit>()])
|
||||
void main() {
|
||||
group('PaymentBanner', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child, OrderInfoCubit cubit) {
|
||||
return MaterialApp.router(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
routerConfig: GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => Portal(
|
||||
child: BlocProvider<OrderInfoCubit>(
|
||||
create: (_) => cubit,
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display payment banner with successful payment', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockOrderInfoCubit();
|
||||
const widget = PaymentBanner(successfulPayment: true);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, mockCubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PaymentBanner), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display payment banner with failed payment', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockOrderInfoCubit();
|
||||
const widget = PaymentBanner(successfulPayment: false);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, mockCubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PaymentBanner), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_props.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_status_box.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
void main() {
|
||||
group('PlanProps', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display plan props with all information', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const planDuration = 30;
|
||||
const planPrice = 1000.0;
|
||||
const planStatus = PlanStatusValues.paid;
|
||||
const widget = PlanProps(
|
||||
planDuration: planDuration,
|
||||
planPrice: planPrice,
|
||||
planStatus: planStatus,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PlanProps), findsOneWidget);
|
||||
expect(find.byType(PlanStatusBox), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display plan props with pending payment status', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const planDuration = 15;
|
||||
const planPrice = 500.0;
|
||||
const planStatus = PlanStatusValues.pendingPayment;
|
||||
const widget = PlanProps(
|
||||
planDuration: planDuration,
|
||||
planPrice: planPrice,
|
||||
planStatus: planStatus,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PlanProps), findsOneWidget);
|
||||
expect(find.byType(PlanStatusBox), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display plan props with payment failed status', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const planDuration = 7;
|
||||
const planPrice = 200.0;
|
||||
const planStatus = PlanStatusValues.paymentFailed;
|
||||
const widget = PlanProps(
|
||||
planDuration: planDuration,
|
||||
planPrice: planPrice,
|
||||
planStatus: planStatus,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PlanProps), findsOneWidget);
|
||||
expect(find.byType(PlanStatusBox), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/widgets/plan_state.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
void main() {
|
||||
group('PlanState', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display plan state without personal diet', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = PlanState(
|
||||
state: ChosenPlanStateEnum.statePaid,
|
||||
hasPersonalDiet: false,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PlanState), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display plan state with personal diet', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = PlanState(
|
||||
state: ChosenPlanStateEnum.statePaid,
|
||||
hasPersonalDiet: true,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PlanState), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display different states correctly', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = PlanState(
|
||||
state: ChosenPlanStateEnum.stateInProgress,
|
||||
hasPersonalDiet: false,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PlanState), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display done state', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = PlanState(
|
||||
state: ChosenPlanStateEnum.stateDone,
|
||||
hasPersonalDiet: false,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PlanState), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display state with personal diet set', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = PlanState(
|
||||
state: ChosenPlanStateEnum.personalDietSet,
|
||||
hasPersonalDiet: true,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PlanState), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,69 +5,146 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../../../fixtures/fixture_reader.dart';
|
||||
import 'personal_diet_suggestion_service_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<Dio>()])
|
||||
@GenerateMocks([Dio])
|
||||
void main() {
|
||||
group('PersonalDietSuggestionService', () {
|
||||
late PersonalDietSuggestionService personalDietSuggestionService;
|
||||
late PersonalDietSuggestionService service;
|
||||
late MockDio mockDio;
|
||||
|
||||
setUp(() {
|
||||
mockDio = MockDio();
|
||||
personalDietSuggestionService = PersonalDietSuggestionService(mockDio);
|
||||
service = PersonalDietSuggestionService(mockDio);
|
||||
});
|
||||
|
||||
test('getConsultationWithPersonalDiets - возвращает консультации с персональными диетами', () async {
|
||||
final json = FixtureReader.readAsMap('consultations/consultation_pagination_response.json');
|
||||
final response = Response(
|
||||
data: json,
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(path: '/api/consultations'),
|
||||
);
|
||||
test('getConsultationWithPersonalDiets - успешное получение консультаций', () async {
|
||||
final mockData = {
|
||||
'data': [],
|
||||
'links': {},
|
||||
'meta': {
|
||||
'current_page': 1,
|
||||
'last_page': 1,
|
||||
'total': 0,
|
||||
'per_page': 10,
|
||||
},
|
||||
};
|
||||
|
||||
when(mockDio.get(any, queryParameters: anyNamed('queryParameters')))
|
||||
.thenAnswer((_) async => response);
|
||||
when(mockDio.get(
|
||||
'/api/consultations',
|
||||
queryParameters: anyNamed('queryParameters'),
|
||||
)).thenAnswer((_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/consultations'),
|
||||
statusCode: 200,
|
||||
data: mockData,
|
||||
));
|
||||
|
||||
final result = await personalDietSuggestionService.getConsultationWithPersonalDiets();
|
||||
final result = await service.getConsultationWithPersonalDiets();
|
||||
|
||||
expect(result, isA<ConsultationPaginationModel>());
|
||||
verify(mockDio.get(any, queryParameters: anyNamed('queryParameters'))).called(1);
|
||||
verify(mockDio.get(
|
||||
'/api/consultations',
|
||||
queryParameters: {
|
||||
'filters[plan][has_personal_diet][\$personal_diet]': '1',
|
||||
'filters[last_state][\$notIn][]': [
|
||||
'done',
|
||||
'charge_was_collected',
|
||||
'complained',
|
||||
'last_day_finished',
|
||||
'canceled_by_the_initiator',
|
||||
'inited'
|
||||
],
|
||||
'sort[]': 'start_date:desc',
|
||||
},
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('attachPersonalDiet - прикрепляет персональную диету', () async {
|
||||
test('getConsultationWithPersonalDiets - пробрасывает ошибку', () async {
|
||||
when(mockDio.get(
|
||||
any,
|
||||
queryParameters: anyNamed('queryParameters'),
|
||||
)).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/api/consultations'),
|
||||
));
|
||||
|
||||
expect(
|
||||
() => service.getConsultationWithPersonalDiets(),
|
||||
throwsA(isA<DioException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('attachPersonalDiet - успешное прикрепление диеты', () async {
|
||||
const consultationId = 1;
|
||||
const dietId = 2;
|
||||
final response = Response(
|
||||
data: {},
|
||||
statusCode: 200,
|
||||
|
||||
when(mockDio.post(
|
||||
'/api/chosen_plans/$consultationId/attach_diet',
|
||||
data: anyNamed('data'),
|
||||
)).thenAnswer((_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/chosen_plans/$consultationId/attach_diet'),
|
||||
statusCode: 200,
|
||||
));
|
||||
|
||||
await service.attachPersonalDiet(consultationId: consultationId, dietId: dietId);
|
||||
|
||||
verify(mockDio.post(
|
||||
'/api/chosen_plans/$consultationId/attach_diet',
|
||||
data: {'diet_id': dietId},
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('attachPersonalDiet - пробрасывает ошибку', () async {
|
||||
const consultationId = 1;
|
||||
const dietId = 2;
|
||||
|
||||
when(mockDio.post(
|
||||
any,
|
||||
data: anyNamed('data'),
|
||||
)).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/api/chosen_plans/$consultationId/attach_diet'),
|
||||
));
|
||||
|
||||
expect(
|
||||
() => service.attachPersonalDiet(consultationId: consultationId, dietId: dietId),
|
||||
throwsA(isA<DioException>()),
|
||||
);
|
||||
|
||||
when(mockDio.post(any, data: anyNamed('data')))
|
||||
.thenAnswer((_) async => response);
|
||||
|
||||
await personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId);
|
||||
|
||||
verify(mockDio.post(any, data: anyNamed('data'))).called(1);
|
||||
});
|
||||
|
||||
test('reattachPersonalDiet - переприкрепляет персональную диету', () async {
|
||||
test('reattachPersonalDiet - успешное повторное прикрепление диеты', () async {
|
||||
const consultationId = 1;
|
||||
const dietId = 2;
|
||||
final response = Response(
|
||||
data: {},
|
||||
statusCode: 200,
|
||||
const dietId = 3;
|
||||
|
||||
when(mockDio.post(
|
||||
'/api/chosen_plans/$consultationId/re_attach_diet',
|
||||
data: anyNamed('data'),
|
||||
)).thenAnswer((_) async => Response(
|
||||
requestOptions: RequestOptions(path: '/api/chosen_plans/$consultationId/re_attach_diet'),
|
||||
statusCode: 200,
|
||||
));
|
||||
|
||||
await service.reattachPersonalDiet(consultationId: consultationId, dietId: dietId);
|
||||
|
||||
verify(mockDio.post(
|
||||
'/api/chosen_plans/$consultationId/re_attach_diet',
|
||||
data: {'diet_id': dietId},
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('reattachPersonalDiet - пробрасывает ошибку', () async {
|
||||
const consultationId = 1;
|
||||
const dietId = 3;
|
||||
|
||||
when(mockDio.post(
|
||||
any,
|
||||
data: anyNamed('data'),
|
||||
)).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/api/chosen_plans/$consultationId/re_attach_diet'),
|
||||
));
|
||||
|
||||
expect(
|
||||
() => service.reattachPersonalDiet(consultationId: consultationId, dietId: dietId),
|
||||
throwsA(isA<DioException>()),
|
||||
);
|
||||
|
||||
when(mockDio.post(any, data: anyNamed('data')))
|
||||
.thenAnswer((_) async => response);
|
||||
|
||||
await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId);
|
||||
|
||||
verify(mockDio.post(any, data: anyNamed('data'))).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/profile/models/weight_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
|
||||
void main() {
|
||||
group('ImtChartWidget', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
await initializeDateFormatting('ru', null);
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Portal(
|
||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display chart with empty weight points', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
const widget = ImtChartWidget(weightPoints: []);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
// Assert
|
||||
expect(find.byType(ImtChartWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display chart with single weight point', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final weightPoints = [
|
||||
WeightModel(
|
||||
id: 1,
|
||||
userId: 1,
|
||||
weight: 70,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
bmi: 23.5,
|
||||
),
|
||||
];
|
||||
final widget = ImtChartWidget(weightPoints: weightPoints);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
// Assert
|
||||
expect(find.byType(ImtChartWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display chart with multiple weight points and show increase arrow', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final weightPoints = [
|
||||
WeightModel(
|
||||
id: 1,
|
||||
userId: 1,
|
||||
weight: 70,
|
||||
createdAt: now.subtract(const Duration(days: 7)),
|
||||
updatedAt: now.subtract(const Duration(days: 7)),
|
||||
bmi: 23.0,
|
||||
),
|
||||
WeightModel(
|
||||
id: 2,
|
||||
userId: 1,
|
||||
weight: 72,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
bmi: 24.0,
|
||||
),
|
||||
];
|
||||
final widget = ImtChartWidget(weightPoints: weightPoints);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
// Assert
|
||||
expect(find.byType(ImtChartWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display chart with multiple weight points and show decrease arrow', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final now = DateTime.now();
|
||||
final weightPoints = [
|
||||
WeightModel(
|
||||
id: 1,
|
||||
userId: 1,
|
||||
weight: 72,
|
||||
createdAt: now.subtract(const Duration(days: 7)),
|
||||
updatedAt: now.subtract(const Duration(days: 7)),
|
||||
bmi: 24.0,
|
||||
),
|
||||
WeightModel(
|
||||
id: 2,
|
||||
userId: 1,
|
||||
weight: 70,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
bmi: 23.0,
|
||||
),
|
||||
];
|
||||
final widget = ImtChartWidget(weightPoints: weightPoints);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
// Assert
|
||||
expect(find.byType(ImtChartWidget), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/phone_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
|
||||
import 'phone_screen_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([RegistrationRepository, Prefs])
|
||||
void main() {
|
||||
group('PhoneScreen', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child, RegistrationCubit cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: BlocProvider<RegistrationCubit>(
|
||||
create: (_) => cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display phone screen with empty phone', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockRepository = MockRegistrationRepository();
|
||||
final cubit = RegistrationCubit(
|
||||
registrationRepository: mockRepository,
|
||||
prefs: MockPrefs(),
|
||||
);
|
||||
const sheet = UserRegistrationSheet(
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'test@example.com',
|
||||
password: 'password',
|
||||
passwordConfirmation: 'password',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
location: 'Moscow',
|
||||
responseBody: '{}',
|
||||
lang: 'ru',
|
||||
weight: 70,
|
||||
height: 175,
|
||||
phone: '',
|
||||
chestCircumference: 90,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 95,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: 0,
|
||||
hasHyper: 0,
|
||||
aboutAllergy: '',
|
||||
aboutTakingPils: '',
|
||||
aboutSportHobbies: '',
|
||||
favoriteFoods: '',
|
||||
unfavoriteFoods: '',
|
||||
additionalInfo: '',
|
||||
);
|
||||
const widget = PhoneScreen(sheet: sheet);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, cubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PhoneScreen), findsOneWidget);
|
||||
expect(find.text('Номер телефона'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display phone screen with pre-filled phone', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockRepository = MockRegistrationRepository();
|
||||
final cubit = RegistrationCubit(
|
||||
registrationRepository: mockRepository,
|
||||
prefs: MockPrefs(),
|
||||
);
|
||||
const sheet = UserRegistrationSheet(
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'test@example.com',
|
||||
password: 'password',
|
||||
passwordConfirmation: 'password',
|
||||
birthdate: '1990-01-01',
|
||||
gender: 'male',
|
||||
location: 'Moscow',
|
||||
responseBody: '{}',
|
||||
lang: 'ru',
|
||||
weight: 70,
|
||||
height: 175,
|
||||
phone: '+7 (999) 123 45 67',
|
||||
chestCircumference: 90,
|
||||
waistCircumference: 80,
|
||||
hipCircumference: 95,
|
||||
bloodPressureUp: 120,
|
||||
bloodPressureDown: 80,
|
||||
hasHypo: 0,
|
||||
hasHyper: 0,
|
||||
aboutAllergy: '',
|
||||
aboutTakingPils: '',
|
||||
aboutSportHobbies: '',
|
||||
favoriteFoods: '',
|
||||
unfavoriteFoods: '',
|
||||
additionalInfo: '',
|
||||
);
|
||||
const widget = PhoneScreen(sheet: sheet);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, cubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PhoneScreen), findsOneWidget);
|
||||
expect(find.text('+7 (999) 123 45 67'), findsOneWidget);
|
||||
|
||||
cubit.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/widgets/change_email_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'change_email_dialog_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<VerifyEmailCubit>()])
|
||||
void main() {
|
||||
group('ChangeEmailDialog', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => child,
|
||||
);
|
||||
},
|
||||
child: const Text('Show Dialog'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display dialog with current email pre-filled', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockVerifyEmailCubit();
|
||||
when(mockCubit.state).thenReturn(const VerifyEmailState());
|
||||
const currentEmail = 'test@example.com';
|
||||
final widget = ChangeEmailDialog(
|
||||
currentEmail: currentEmail,
|
||||
verifyEmailCubit: mockCubit,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.tap(find.text('Show Dialog'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.text('Сменить email'), findsOneWidget);
|
||||
expect(find.text(currentEmail), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show error for empty email', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockVerifyEmailCubit();
|
||||
when(mockCubit.state).thenReturn(const VerifyEmailState());
|
||||
const currentEmail = 'test@example.com';
|
||||
final widget = ChangeEmailDialog(
|
||||
currentEmail: currentEmail,
|
||||
verifyEmailCubit: mockCubit,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.tap(find.text('Show Dialog'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final textField = find.byType(TextField);
|
||||
await tester.enterText(textField, '');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.text('Введите email'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should close dialog when Отмена button is tapped', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockVerifyEmailCubit();
|
||||
when(mockCubit.state).thenReturn(const VerifyEmailState());
|
||||
const currentEmail = 'test@example.com';
|
||||
final widget = ChangeEmailDialog(
|
||||
currentEmail: currentEmail,
|
||||
verifyEmailCubit: mockCubit,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.tap(find.text('Show Dialog'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Отмена'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.text('Сменить email'), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/widgets/code_not_received_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
import 'code_not_received_dialog_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<VerifyEmailCubit>()])
|
||||
void main() {
|
||||
group('CodeNotReceivedDialog', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => child,
|
||||
);
|
||||
},
|
||||
child: const Text('Show Dialog'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display dialog with correct text', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockVerifyEmailCubit();
|
||||
const currentEmail = 'test@example.com';
|
||||
final widget = CodeNotReceivedDialog(
|
||||
currentEmail: currentEmail,
|
||||
verifyEmailCubit: mockCubit,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.tap(find.text('Show Dialog'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.text('Почему может не приходить код?'), findsOneWidget);
|
||||
expect(find.text('Понятно'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should close dialog when Понятно button is tapped', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockVerifyEmailCubit();
|
||||
const currentEmail = 'test@example.com';
|
||||
final widget = CodeNotReceivedDialog(
|
||||
currentEmail: currentEmail,
|
||||
verifyEmailCubit: mockCubit,
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget));
|
||||
await tester.tap(find.text('Show Dialog'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Понятно'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.text('Почему может не приходить код?'), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
|
||||
import 'package:eatvio_app/features/registration_v2/presentation/widgets/password_field.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'password_field_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<RegistrationCubitV2>()])
|
||||
void main() {
|
||||
group('PasswordField', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child, RegistrationCubitV2 cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: BlocProvider<RegistrationCubitV2>(
|
||||
create: (_) => cubit,
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display password field with password from state', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockRegistrationCubitV2();
|
||||
const testPassword = 'testPassword123';
|
||||
when(mockCubit.state).thenReturn(
|
||||
RegistrationStateV2(password: testPassword),
|
||||
);
|
||||
final widget = PasswordField(cubit: mockCubit);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, mockCubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PasswordField), findsOneWidget);
|
||||
expect(find.text(testPassword), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should show copy icon initially', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockRegistrationCubitV2();
|
||||
when(mockCubit.state).thenReturn(
|
||||
RegistrationStateV2(password: 'test'),
|
||||
);
|
||||
final widget = PasswordField(cubit: mockCubit);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(widget, mockCubit));
|
||||
await tester.pump();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(PasswordField), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
|
||||
import 'package:eatvio_app/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'show_successful_registration_bottom_sheet_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<RegistrationCubitV2>()])
|
||||
void main() {
|
||||
group('ShowSuccessfulRegistrationBottomSheet', () {
|
||||
setUp(() async {
|
||||
await sl.reset();
|
||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||
});
|
||||
|
||||
Widget createTestWidget(Widget child, RegistrationCubitV2 cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: BlocProvider<RegistrationCubitV2>(
|
||||
create: (_) => cubit,
|
||||
child: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => ElevatedButton(
|
||||
onPressed: () {
|
||||
showRegistrationBottomSheet(context, cubit, true);
|
||||
},
|
||||
child: const Text('Show Bottom Sheet'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should display success bottom sheet when isSuccess is true', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockRegistrationCubitV2();
|
||||
when(mockCubit.state).thenReturn(
|
||||
RegistrationStateV2(password: 'testPassword'),
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidget(const SizedBox(), mockCubit));
|
||||
await tester.tap(find.text('Show Bottom Sheet'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(RegistrationBottomSheet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display error bottom sheet when isSuccess is false', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
final mockCubit = MockRegistrationCubitV2();
|
||||
when(mockCubit.state).thenReturn(
|
||||
RegistrationStateV2(),
|
||||
);
|
||||
|
||||
Widget createTestWidgetError(Widget child, RegistrationCubitV2 cubit) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [Locale('ru')],
|
||||
home: Portal(
|
||||
child: BlocProvider<RegistrationCubitV2>(
|
||||
create: (_) => cubit,
|
||||
child: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => ElevatedButton(
|
||||
onPressed: () {
|
||||
showRegistrationBottomSheet(context, cubit, false);
|
||||
},
|
||||
child: const Text('Show Bottom Sheet'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(createTestWidgetError(const SizedBox(), mockCubit));
|
||||
await tester.tap(find.text('Show Bottom Sheet'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.byType(RegistrationBottomSheet), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user