forked from eatvio/eatvio_app
fix: issue-372, issue-227, issue-406
This commit is contained in:
@@ -122,7 +122,7 @@ class CubitRegistrations {
|
|||||||
|
|
||||||
// Content viewing cubits
|
// Content viewing cubits
|
||||||
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
||||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl(), profileRepository: sl(), dietRepository: sl()));
|
||||||
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
|
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
|
||||||
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
||||||
|
|
||||||
|
|||||||
61
lib/core/utils/inline_svg_css.dart
Normal file
61
lib/core/utils/inline_svg_css.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/// flutter_svg / vector_graphics ignore `<style>` blocks.
|
||||||
|
/// Paths then get the SVG default fill (black), so badges look like a black blob.
|
||||||
|
/// This inlines class rules from `<style>` as attributes before parsing.
|
||||||
|
String inlineSvgCss(String svg) {
|
||||||
|
final styleBlockPattern = RegExp(
|
||||||
|
r'<style[^>]*>(?:<!\[CDATA\[)?([\s\S]*?)(?:\]\]>)?</style>',
|
||||||
|
caseSensitive: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final classProps = <String, Map<String, String>>{};
|
||||||
|
for (final match in styleBlockPattern.allMatches(svg)) {
|
||||||
|
final css = match.group(1) ?? '';
|
||||||
|
final rulePattern = RegExp(r'\.([A-Za-z0-9_-]+)\s*\{([^}]*)\}');
|
||||||
|
for (final rule in rulePattern.allMatches(css)) {
|
||||||
|
final className = rule.group(1)!;
|
||||||
|
final body = rule.group(2)!;
|
||||||
|
final props = classProps.putIfAbsent(className, () => <String, String>{});
|
||||||
|
for (final declaration in body.split(';')) {
|
||||||
|
final colon = declaration.indexOf(':');
|
||||||
|
if (colon <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final key = declaration.substring(0, colon).trim();
|
||||||
|
final value = declaration.substring(colon + 1).trim();
|
||||||
|
if (key.isNotEmpty && value.isNotEmpty) {
|
||||||
|
props[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = svg.replaceAll(styleBlockPattern, '');
|
||||||
|
if (classProps.isEmpty) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.replaceAllMapped(RegExp(r'''\sclass=(["'])([^"']+)\1'''), (
|
||||||
|
Match match,
|
||||||
|
) {
|
||||||
|
final classes = match
|
||||||
|
.group(2)!
|
||||||
|
.split(RegExp(r'\s+'))
|
||||||
|
.where((name) => name.isNotEmpty);
|
||||||
|
final attrs = <String, String>{};
|
||||||
|
for (final className in classes) {
|
||||||
|
final props = classProps[className];
|
||||||
|
if (props != null) {
|
||||||
|
attrs.addAll(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attrs.isEmpty) {
|
||||||
|
return match.group(0)!;
|
||||||
|
}
|
||||||
|
final attrString = attrs.entries
|
||||||
|
.map((entry) => '${entry.key}="${entry.value}"')
|
||||||
|
.join(' ');
|
||||||
|
return ' $attrString';
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
57
lib/core/widgets/network_svg_picture.dart
Normal file
57
lib/core/widgets/network_svg_picture.dart
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import 'package:eatvio_app/core/utils/inline_svg_css.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
final Map<String, String> _inlinedSvgCache = <String, String>{};
|
||||||
|
|
||||||
|
class NetworkSvgPicture extends StatefulWidget {
|
||||||
|
const NetworkSvgPicture({
|
||||||
|
super.key,
|
||||||
|
required this.url,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String url;
|
||||||
|
final double? width;
|
||||||
|
final double? height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NetworkSvgPicture> createState() => _NetworkSvgPictureState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NetworkSvgPictureState extends State<NetworkSvgPicture> {
|
||||||
|
late final Future<String> _svgFuture = _load(widget.url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<String>(
|
||||||
|
future: _svgFuture,
|
||||||
|
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return SizedBox(width: widget.width, height: widget.height);
|
||||||
|
}
|
||||||
|
return SvgPicture.string(
|
||||||
|
snapshot.data!,
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String> _load(String url) async {
|
||||||
|
final cached = _inlinedSvgCache[url];
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
final response = await http.get(Uri.parse(url));
|
||||||
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||||
|
throw Exception('Failed to load SVG from $url');
|
||||||
|
}
|
||||||
|
final processed = inlineSvgCss(response.body);
|
||||||
|
_inlinedSvgCache[url] = processed;
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -763,7 +763,6 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 16),
|
|
||||||
const Text('Заголовок'),
|
const Text('Заголовок'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CustomTextFormField(
|
CustomTextFormField(
|
||||||
|
|||||||
@@ -26,16 +26,47 @@ class PostFeedMapper {
|
|||||||
totalViews: item.postViewsCount,
|
totalViews: item.postViewsCount,
|
||||||
totalComments: item.postCommentsCount,
|
totalComments: item.postCommentsCount,
|
||||||
wasLikedByMe: item.postIsLikedByUser,
|
wasLikedByMe: item.postIsLikedByUser,
|
||||||
images: item.images,
|
images: item.images.where((image) => hasRenderableImage(image.url)).toList(),
|
||||||
videoEmbedUrl: item.videoEmbedUrl,
|
videoEmbedUrl: item.videoEmbedUrl,
|
||||||
videoUrlThumb: item.videoUrlThumb,
|
videoUrlThumb: item.videoUrlThumb,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String resolveMediaUrl(String? url) {
|
static String resolveMediaUrl(String? url) {
|
||||||
if (url == null || url.isEmpty) return '';
|
if (url == null || url.isEmpty || isPlaceholderMediaUrl(url)) {
|
||||||
if (url.startsWith('http://') || url.startsWith('https://')) return url;
|
return '';
|
||||||
if (url.startsWith('/')) return '${sl<AppConfig>().baseUrl}$url';
|
}
|
||||||
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
if (url.startsWith('/')) {
|
||||||
|
return '${sl<AppConfig>().baseUrl}$url';
|
||||||
|
}
|
||||||
return '${sl<AppConfig>().baseUrl}/storage/$url';
|
return '${sl<AppConfig>().baseUrl}/storage/$url';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isPlaceholderMediaUrl(String url) {
|
||||||
|
return url.toLowerCase().contains('element-image-placeholder');
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasRenderableImage(String? url) {
|
||||||
|
if (url == null || url.trim().isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !isPlaceholderMediaUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasVisibleMedia({
|
||||||
|
required String image,
|
||||||
|
required List<FeedGalleryImage> images,
|
||||||
|
String? videoEmbedUrl,
|
||||||
|
}) {
|
||||||
|
if (videoEmbedUrl != null && videoEmbedUrl.trim().isNotEmpty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (hasRenderableImage(image)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return images.any((image) => hasRenderableImage(image.url));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ class _BlogPostMediaContentState extends State<BlogPostMediaContent> {
|
|||||||
return SizedBox(height: mediaHeight, width: width, child: _videoSlide());
|
return SizedBox(height: mediaHeight, width: width, child: _videoSlide());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.imageUrl.isNotEmpty) {
|
if (PostFeedMapper.hasRenderableImage(widget.imageUrl)) {
|
||||||
final image = _photo(widget.imageUrl, width: width);
|
final image = _photo(widget.imageUrl, width: width);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => widget.onOpenGallery([widget.imageUrl], 0),
|
onTap: () => widget.onOpenGallery([widget.imageUrl], 0),
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final imageUrl = PostFeedMapper.resolveMediaUrl(post.image);
|
final imageUrl = PostFeedMapper.resolveMediaUrl(post.image);
|
||||||
final embedUrl = post.videoEmbedUrl?.trim();
|
final embedUrl = post.videoEmbedUrl?.trim();
|
||||||
|
final hasMedia = PostFeedMapper.hasVisibleMedia(
|
||||||
|
image: imageUrl,
|
||||||
|
images: post.images,
|
||||||
|
videoEmbedUrl: embedUrl,
|
||||||
|
);
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -97,30 +102,27 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_BlogPostHeader(post: post),
|
_BlogPostHeader(post: post),
|
||||||
_BlogPostTextContent(post: post),
|
_BlogPostTextContent(post: post, includeBottomSpacing: hasMedia),
|
||||||
BlogPostMediaContent(
|
if (hasMedia)
|
||||||
galleryImages: post.images,
|
BlogPostMediaContent(
|
||||||
imageUrl: imageUrl,
|
galleryImages: post.images,
|
||||||
videoEmbedUrl: embedUrl != null && embedUrl.isNotEmpty
|
imageUrl: imageUrl,
|
||||||
? embedUrl
|
videoEmbedUrl: embedUrl != null && embedUrl.isNotEmpty ? embedUrl : null,
|
||||||
: null,
|
videoUrlThumb: post.videoUrlThumb,
|
||||||
videoUrlThumb: post.videoUrlThumb,
|
currentGalleryIndex: currentGalleryIndex,
|
||||||
currentGalleryIndex: currentGalleryIndex,
|
heroTag: 'feed-blog-post-${post.id}-cover',
|
||||||
heroTag: 'feed-blog-post-${post.id}-cover',
|
onOpenGallery: (urls, index) => _openGallery(
|
||||||
onOpenGallery: (urls, index) => _openGallery(
|
urls,
|
||||||
urls,
|
index,
|
||||||
index,
|
heroTag: urls.length == 1 ? 'feed-blog-post-${post.id}-cover' : null,
|
||||||
heroTag: urls.length == 1
|
),
|
||||||
? 'feed-blog-post-${post.id}-cover'
|
onGalleryChanged: (index) {
|
||||||
: null,
|
setState(() {
|
||||||
|
currentGalleryIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onGalleryChanged: (index) {
|
SizedBox(height: hasMedia ? 12 : 8),
|
||||||
setState(() {
|
|
||||||
currentGalleryIndex = index;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_BlogPostActionsRow(
|
_BlogPostActionsRow(
|
||||||
post: post,
|
post: post,
|
||||||
isLiked: isLiked,
|
isLiked: isLiked,
|
||||||
@@ -213,9 +215,10 @@ class _BlogPostHeader extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _BlogPostTextContent extends StatelessWidget {
|
class _BlogPostTextContent extends StatelessWidget {
|
||||||
const _BlogPostTextContent({required this.post});
|
const _BlogPostTextContent({required this.post, required this.includeBottomSpacing});
|
||||||
|
|
||||||
final FeedBlogPostModel post;
|
final FeedBlogPostModel post;
|
||||||
|
final bool includeBottomSpacing;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -259,7 +262,7 @@ class _BlogPostTextContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (post.title.isNotEmpty || post.description.isNotEmpty)
|
if (includeBottomSpacing && (post.title.isNotEmpty || post.description.isNotEmpty))
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:eatvio_app/core/app_config.dart';
|
import 'package:eatvio_app/core/app_config.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/network_svg_picture.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
|
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
|
||||||
import 'package:eatvio_app/features/profile/models/badge_model.dart';
|
import 'package:eatvio_app/features/profile/models/badge_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -92,7 +93,7 @@ class ConsultantWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < badges.length; i++) ...[
|
for (int i = 0; i < badges.length; i++) ...[
|
||||||
badges[i].image.contains('svg')
|
badges[i].image.contains('svg')
|
||||||
? SvgPicture.network('${sl<AppConfig>().baseUrl}/storage/${badges[i].image}', width: 28, height: 28)
|
? NetworkSvgPicture(url: '${sl<AppConfig>().baseUrl}/storage/${badges[i].image}', width: 28, height: 28)
|
||||||
: CachedNetworkImage(imageUrl: '${sl<AppConfig>().baseUrl}/storage/${badges[i].image}', width: 28, height: 28),
|
: CachedNetworkImage(imageUrl: '${sl<AppConfig>().baseUrl}/storage/${badges[i].image}', width: 28, height: 28),
|
||||||
i < badges.length - 1 ? const SizedBox(width: 8) : const SizedBox.shrink(),
|
i < badges.length - 1 ? const SizedBox(width: 8) : const SizedBox.shrink(),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget {
|
|||||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
|
buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.green)),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
GoRouter.of(context).push(CardSettingsScreen.path, extra: notification.planId);
|
GoRouter.of(context).push(CardSettingsScreen.path, extra: notification.chosenPlanId);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -34,16 +34,28 @@ class InfoByOrderScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => sl<OrderInfoCubit>()..getChosenPlan(id: planId, customerSlug: customerSlug, isConsultantView: isConsultantView),
|
create: (context) => sl<OrderInfoCubit>()..getChosenPlan(id: planId, customerSlug: customerSlug, isConsultantView: isConsultantView),
|
||||||
child: InfoByOrderScreenContent(successfulPayment: successfulPayment, planId: planId, customerSlug: customerSlug),
|
child: InfoByOrderScreenContent(
|
||||||
|
successfulPayment: successfulPayment,
|
||||||
|
planId: planId,
|
||||||
|
customerSlug: customerSlug,
|
||||||
|
isConsultantView: isConsultantView,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InfoByOrderScreenContent extends StatefulWidget {
|
class InfoByOrderScreenContent extends StatefulWidget {
|
||||||
const InfoByOrderScreenContent({super.key, required this.planId, required this.successfulPayment, this.customerSlug});
|
const InfoByOrderScreenContent({
|
||||||
|
super.key,
|
||||||
|
required this.planId,
|
||||||
|
required this.successfulPayment,
|
||||||
|
this.customerSlug,
|
||||||
|
this.isConsultantView = false,
|
||||||
|
});
|
||||||
final int planId;
|
final int planId;
|
||||||
final bool? successfulPayment;
|
final bool? successfulPayment;
|
||||||
final String? customerSlug;
|
final String? customerSlug;
|
||||||
|
final bool isConsultantView;
|
||||||
@override
|
@override
|
||||||
State<InfoByOrderScreenContent> createState() => _InfoByOrderScreenContentState();
|
State<InfoByOrderScreenContent> createState() => _InfoByOrderScreenContentState();
|
||||||
}
|
}
|
||||||
@@ -131,7 +143,9 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
|
|||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (plan.chosenPlanState == ChosenPlanStateEnum.stateDone) ...[CollectChargeBanner(chosenPlan: plan, price: chosenOption?.price ?? 0)],
|
if (plan.chosenPlanState == ChosenPlanStateEnum.stateDone && (widget.customerSlug != null || widget.isConsultantView)) ...[
|
||||||
|
CollectChargeBanner(chosenPlan: plan, price: chosenOption?.price ?? 0),
|
||||||
|
],
|
||||||
if (widget.successfulPayment != null) ...[const SizedBox(height: 8), PaymentBanner(successfulPayment: widget.successfulPayment ?? false)],
|
if (widget.successfulPayment != null) ...[const SizedBox(height: 8), PaymentBanner(successfulPayment: widget.successfulPayment ?? false)],
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
AuthorCard(
|
AuthorCard(
|
||||||
|
|||||||
@@ -7,12 +7,21 @@ import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
|||||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
||||||
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/models/chosen_diets_pagination_model.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||||
|
|
||||||
class AboutPlanCubit extends Cubit<AboutPlanState> {
|
class AboutPlanCubit extends Cubit<AboutPlanState> {
|
||||||
final PlanRepository planRepository;
|
final PlanRepository planRepository;
|
||||||
AboutPlanCubit({required this.planRepository}) : super(AboutPlanState.initial());
|
final ProfileRepository profileRepository;
|
||||||
|
final DietRepository dietRepository;
|
||||||
|
|
||||||
|
AboutPlanCubit({
|
||||||
|
required this.planRepository,
|
||||||
|
required this.profileRepository,
|
||||||
|
required this.dietRepository,
|
||||||
|
}) : super(AboutPlanState.initial());
|
||||||
|
|
||||||
Future<void> getPlan({required String slug, int? index}) async {
|
Future<void> getPlan({required String slug, int? index}) async {
|
||||||
try {
|
try {
|
||||||
@@ -141,6 +150,38 @@ class AboutPlanCubit extends Cubit<AboutPlanState> {
|
|||||||
return (null, '');
|
return (null, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Активная публичная диета (без привязки к плану) в статусах inited/started.
|
||||||
|
Future<ChosenDietModel?> getActivePublicDiet() async {
|
||||||
|
try {
|
||||||
|
final chosenDiets = await profileRepository.getChosenDiets();
|
||||||
|
final lastChosenDiet = chosenDiets.firstOrNull;
|
||||||
|
if (lastChosenDiet == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final isActive = ['inited', 'started'].contains(lastChosenDiet.state);
|
||||||
|
final isPublic = lastChosenDiet.chosenPlanId == 0;
|
||||||
|
if (isActive && isPublic && lastChosenDiet.dietId != null) {
|
||||||
|
return lastChosenDiet;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> cancelPublicDiet(int dietId) async {
|
||||||
|
try {
|
||||||
|
await dietRepository.cancelDiet(dietId);
|
||||||
|
return true;
|
||||||
|
} catch (e, st) {
|
||||||
|
await reportFrom(e, st, this);
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _getOptionNameFromIndex(int index) => switch (index) {
|
String _getOptionNameFromIndex(int index) => switch (index) {
|
||||||
1 => 'options2',
|
1 => 'options2',
|
||||||
2 => 'options3',
|
2 => 'options3',
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
|
|||||||
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.dart';
|
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/presentation/widgets/confirm_start_plan_bottom_sheet.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/widgets/options_tab.dart';
|
import 'package:eatvio_app/features/plans/presentation/widgets/options_tab.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
@@ -73,6 +75,51 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
|||||||
scrollController.jumpTo(scrollController.position.maxScrollExtent);
|
scrollController.jumpTo(scrollController.position.maxScrollExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onOrderPressed(BuildContext context) async {
|
||||||
|
final cubit = context.read<AboutPlanCubit>();
|
||||||
|
final publicDiet = await cubit.getActivePublicDiet();
|
||||||
|
if (!context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publicDiet != null) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => ConfirmStartPlanBottomSheet(
|
||||||
|
onConfirm: () => _submitOrder(context, cubit, publicDietId: publicDiet.dietId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _submitOrder(context, cubit);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submitOrder(BuildContext context, AboutPlanCubit cubit, {int? publicDietId}) async {
|
||||||
|
final s = S.of(context);
|
||||||
|
if (publicDietId != null) {
|
||||||
|
final cancelled = await cubit.cancelPublicDiet(publicDietId);
|
||||||
|
if (!cancelled) {
|
||||||
|
showErrorToast('Не удалось отменить диету');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final (chosenPlan, error) = await cubit.chosePlan();
|
||||||
|
if (!context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (error == 'hasPlan') {
|
||||||
|
showErrorDialog(context, title: s.end_previous_plan, description: s.you_can_not_start_plan_until_end_of_prev_one);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chosenPlan == null) {
|
||||||
|
showErrorDialog(context, title: 'Не удалось заказать план');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).pushReplacementNamed('chatScreen', extra: {'uuid': chosenPlan.chatId, 'sendInitialMessage': true});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
@@ -183,20 +230,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
|||||||
focusElevation: 0,
|
focusElevation: 0,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
backgroundColor: appTheme.greenA700,
|
backgroundColor: appTheme.greenA700,
|
||||||
onPressed: () async {
|
onPressed: () => _onOrderPressed(context),
|
||||||
final (chosenPlan, error) = await context.read<AboutPlanCubit>().chosePlan();
|
|
||||||
if (error == 'hasPlan') {
|
|
||||||
showErrorDialog(context, title: s.end_previous_plan, description: s.you_can_not_start_plan_until_end_of_prev_one);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (chosenPlan == null) {
|
|
||||||
if (!context.mounted) return;
|
|
||||||
showErrorDialog(context, title: 'Не удалось заказать план');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!context.mounted) return;
|
|
||||||
GoRouter.of(context).pushReplacementNamed('chatScreen', extra: {'uuid': chosenPlan.chatId, 'sendInitialMessage': true});
|
|
||||||
},
|
|
||||||
label: Text(s.to_order, style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700)),
|
label: Text(s.to_order, style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ConfirmStartPlanBottomSheet extends StatelessWidget {
|
||||||
|
const ConfirmStartPlanBottomSheet({super.key, required this.onConfirm});
|
||||||
|
|
||||||
|
final Future<void> Function() onConfirm;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final s = S.of(context);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
const BottomSheetHandlerBar(),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
|
child: Text(
|
||||||
|
'Вы уверены, что хотите начать прохождение плана?',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 28),
|
||||||
|
child: CustomOutlinedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await Navigator.of(context).maybePop();
|
||||||
|
await onConfirm();
|
||||||
|
},
|
||||||
|
height: 60,
|
||||||
|
text: s.confirm,
|
||||||
|
buttonTextStyle: TextStyles.textLSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||||
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ abstract class ChosenDietsPaginationModel with _$ChosenDietsPaginationModel {
|
|||||||
abstract class ChosenDietModel with _$ChosenDietModel {
|
abstract class ChosenDietModel with _$ChosenDietModel {
|
||||||
factory ChosenDietModel({
|
factory ChosenDietModel({
|
||||||
@JsonKey(name: 'id') required int chosenDietID,
|
@JsonKey(name: 'id') required int chosenDietID,
|
||||||
@JsonKey(name: 'chosen_plan_id') required int chosenPlanId,
|
@JsonKey(name: 'chosen_plan_id', fromJson: _parseChosenPlanId) required int chosenPlanId,
|
||||||
@JsonKey() required String state,
|
@JsonKey() required String state,
|
||||||
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
|
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
|
||||||
@JsonKey(name: 'diet', fromJson: _parseDietId) required int? dietId,
|
@JsonKey(name: 'diet', fromJson: _parseDietId) required int? dietId,
|
||||||
@@ -30,3 +30,9 @@ int? _parseDietId(dynamic json) {
|
|||||||
if (json == null) return null;
|
if (json == null) return null;
|
||||||
return json['id'] as int?;
|
return json['id'] as int?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _parseChosenPlanId(dynamic json) {
|
||||||
|
if (json == null) return 0;
|
||||||
|
if (json is num) return json.toInt();
|
||||||
|
return int.tryParse(json.toString()) ?? 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
|||||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/network_svg_picture.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||||
@@ -26,6 +27,7 @@ import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/prof
|
|||||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart';
|
import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/presentation/widgets/expandable_page_view.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart';
|
import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_post_widget.dart';
|
import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_post_widget.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart';
|
import 'package:eatvio_app/features/profile/presentation/widgets/toggle_cert_photo_bottom_sheet.dart';
|
||||||
@@ -382,19 +384,16 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SizedBox(
|
ExpandablePageView(
|
||||||
height: 781,
|
itemCount: state.plansList.length,
|
||||||
child: PageView.builder(
|
onPageChanged: (bullet) {
|
||||||
itemBuilder: (BuildContext context, int index) {
|
setState(() {
|
||||||
return PlanWidget(plan: state.plansList[index], index: index);
|
_currentBullet = bullet;
|
||||||
},
|
});
|
||||||
onPageChanged: (bullet) {
|
},
|
||||||
setState(() {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
_currentBullet = bullet;
|
return PlanWidget(plan: state.plansList[index], index: index);
|
||||||
});
|
},
|
||||||
},
|
|
||||||
itemCount: state.plansList.length,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_bullets(_currentBullet, state.plansList.length),
|
_bullets(_currentBullet, state.plansList.length),
|
||||||
@@ -779,7 +778,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
boxShadow: [BoxShadow(color: Colors.black.withAlpha((255 * 0.2).toInt()), blurRadius: 32)],
|
boxShadow: [BoxShadow(color: Colors.black.withAlpha((255 * 0.2).toInt()), blurRadius: 32)],
|
||||||
),
|
),
|
||||||
child: SvgPicture.network('${sl<AppConfig>().baseUrl}/storage/${state.profile.badges[i].image}', width: 40, height: 40),
|
child: NetworkSvgPicture(
|
||||||
|
url: '${sl<AppConfig>().baseUrl}/storage/${state.profile.badges[i].image}',
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: Tooltip(
|
: Tooltip(
|
||||||
preferBelow: true,
|
preferBelow: true,
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ExpandablePageView extends StatefulWidget {
|
||||||
|
const ExpandablePageView({
|
||||||
|
super.key,
|
||||||
|
required this.itemCount,
|
||||||
|
required this.itemBuilder,
|
||||||
|
this.onPageChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int itemCount;
|
||||||
|
final IndexedWidgetBuilder itemBuilder;
|
||||||
|
final ValueChanged<int>? onPageChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ExpandablePageView> createState() => _ExpandablePageViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpandablePageViewState extends State<ExpandablePageView> {
|
||||||
|
late List<double> _heights;
|
||||||
|
int _currentPage = 0;
|
||||||
|
|
||||||
|
double get _currentHeight {
|
||||||
|
if (_heights.isEmpty) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final page = _currentPage.clamp(0, _heights.length - 1);
|
||||||
|
return _heights[page];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_heights = List<double>.filled(widget.itemCount, 0, growable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant ExpandablePageView oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.itemCount != oldWidget.itemCount) {
|
||||||
|
_heights = List<double>.filled(widget.itemCount, 0, growable: true);
|
||||||
|
if (widget.itemCount == 0) {
|
||||||
|
_currentPage = 0;
|
||||||
|
} else {
|
||||||
|
_currentPage = _currentPage.clamp(0, widget.itemCount - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: _currentHeight,
|
||||||
|
child: PageView.builder(
|
||||||
|
itemCount: widget.itemCount,
|
||||||
|
onPageChanged: (int index) {
|
||||||
|
setState(() {
|
||||||
|
_currentPage = index;
|
||||||
|
});
|
||||||
|
widget.onPageChanged?.call(index);
|
||||||
|
},
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return OverflowBox(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
minHeight: 0,
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: _SizeReportingWidget(
|
||||||
|
onSizeChange: (Size size) {
|
||||||
|
if (index >= _heights.length ||
|
||||||
|
_heights[index] == size.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_heights[index] = size.height;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: widget.itemBuilder(context, index),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SizeReportingWidget extends StatefulWidget {
|
||||||
|
const _SizeReportingWidget({required this.child, required this.onSizeChange});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final ValueChanged<Size> onSizeChange;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_SizeReportingWidget> createState() => _SizeReportingWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SizeReportingWidgetState extends State<_SizeReportingWidget> {
|
||||||
|
Size? _oldSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _notifySize());
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _notifySize() {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final size = context.size;
|
||||||
|
if (size != null && _oldSize != size) {
|
||||||
|
_oldSize = size;
|
||||||
|
widget.onSizeChange(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,11 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
|
|||||||
totalLikes = post.totalLikes;
|
totalLikes = post.totalLikes;
|
||||||
final imageUrl = PostFeedMapper.resolveMediaUrl(post.image);
|
final imageUrl = PostFeedMapper.resolveMediaUrl(post.image);
|
||||||
final embedUrl = post.videoEmbedUrl?.trim();
|
final embedUrl = post.videoEmbedUrl?.trim();
|
||||||
|
final hasMedia = PostFeedMapper.hasVisibleMedia(
|
||||||
|
image: imageUrl,
|
||||||
|
images: post.images,
|
||||||
|
videoEmbedUrl: embedUrl,
|
||||||
|
);
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -79,40 +84,12 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
Text(
|
||||||
radius: 20,
|
'${calculateDifference(post.createdAt)} в ${post.createdAt.hour.toString().padLeft(2, '0')}:${post.createdAt.minute.toString().padLeft(2, '0')}',
|
||||||
backgroundColor: Colors.black12,
|
style: const TextStyle(
|
||||||
foregroundImage: NetworkImage(
|
height: 1,
|
||||||
post.author.authorProfileIcon.contains('https')
|
color: Colors.black45,
|
||||||
? post.author.authorProfileIcon
|
|
||||||
: sl<AppConfig>().baseUrl +
|
|
||||||
post.author.authorProfileIcon,
|
|
||||||
),
|
),
|
||||||
backgroundImage: const AssetImage(
|
|
||||||
'assets/images/no_avatar.png',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
post.author.authorName,
|
|
||||||
style: const TextStyle(
|
|
||||||
height: 1,
|
|
||||||
color: Colors.black87,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${calculateDifference(post.createdAt)} в ${post.createdAt.hour.toString().padLeft(2, '0')}:${post.createdAt.minute.toString().padLeft(2, '0')}',
|
|
||||||
style: const TextStyle(
|
|
||||||
height: 1,
|
|
||||||
color: Colors.black45,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
@@ -167,33 +144,34 @@ class _ProfileBlogPostWidgetState extends State<ProfileBlogPostWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (post.title.isNotEmpty || post.description.isNotEmpty)
|
if (hasMedia && (post.title.isNotEmpty || post.description.isNotEmpty))
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
BlogPostMediaContent(
|
if (hasMedia)
|
||||||
galleryImages: post.images,
|
BlogPostMediaContent(
|
||||||
imageUrl: imageUrl,
|
galleryImages: post.images,
|
||||||
videoEmbedUrl: embedUrl != null && embedUrl.isNotEmpty
|
imageUrl: imageUrl,
|
||||||
? embedUrl
|
videoEmbedUrl: embedUrl != null && embedUrl.isNotEmpty
|
||||||
: null,
|
? embedUrl
|
||||||
videoUrlThumb: post.videoUrlThumb != null
|
|
||||||
? PostFeedMapper.resolveMediaUrl(post.videoUrlThumb)
|
|
||||||
: null,
|
|
||||||
currentGalleryIndex: currentGalleryIndex,
|
|
||||||
heroTag: 'profile-blog-post-${post.id}-cover',
|
|
||||||
onOpenGallery: (urls, index) => _openGallery(
|
|
||||||
urls,
|
|
||||||
index,
|
|
||||||
heroTag: urls.length == 1
|
|
||||||
? 'profile-blog-post-${post.id}-cover'
|
|
||||||
: null,
|
: null,
|
||||||
|
videoUrlThumb: post.videoUrlThumb != null
|
||||||
|
? PostFeedMapper.resolveMediaUrl(post.videoUrlThumb)
|
||||||
|
: null,
|
||||||
|
currentGalleryIndex: currentGalleryIndex,
|
||||||
|
heroTag: 'profile-blog-post-${post.id}-cover',
|
||||||
|
onOpenGallery: (urls, index) => _openGallery(
|
||||||
|
urls,
|
||||||
|
index,
|
||||||
|
heroTag: urls.length == 1
|
||||||
|
? 'profile-blog-post-${post.id}-cover'
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
onGalleryChanged: (index) {
|
||||||
|
setState(() {
|
||||||
|
currentGalleryIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onGalleryChanged: (index) {
|
SizedBox(height: hasMedia ? 12 : 8),
|
||||||
setState(() {
|
|
||||||
currentGalleryIndex = index;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: eatvio_app
|
name: eatvio_app
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
version: 1.3.44+133
|
version: 1.3.45+134
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
36
test/core/utils/inline_svg_css_test.dart
Normal file
36
test/core/utils/inline_svg_css_test.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:eatvio_app/core/utils/inline_svg_css.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('inlineSvgCss', () {
|
||||||
|
test(
|
||||||
|
'inlines class fills from a style block so flutter_svg can paint colors',
|
||||||
|
() {
|
||||||
|
const svg = '''
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFD200;}
|
||||||
|
.st1{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<circle class="st0" cx="20" cy="20" r="18"/>
|
||||||
|
<path class="st1" d="M10 20h20"/>
|
||||||
|
</svg>
|
||||||
|
''';
|
||||||
|
|
||||||
|
final result = inlineSvgCss(svg);
|
||||||
|
|
||||||
|
expect(result, isNot(contains('<style')));
|
||||||
|
expect(result, contains('fill="#FFD200"'));
|
||||||
|
expect(result, contains('fill="#FFFFFF"'));
|
||||||
|
expect(result, isNot(contains('class="st0"')));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('keeps SVG without style blocks unchanged', () {
|
||||||
|
const svg =
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg"><circle fill="#00AA00" cx="10" cy="10" r="5"/></svg>';
|
||||||
|
|
||||||
|
expect(inlineSvgCss(svg), svg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -128,6 +128,15 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('does not add extra space above the title field', (tester) async {
|
||||||
|
await tester.pumpWidget(createTestWidget());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final titleTop = tester.getTopLeft(find.text('Заголовок')).dy;
|
||||||
|
final scrollTop = tester.getTopLeft(find.byType(SingleChildScrollView)).dy;
|
||||||
|
expect(titleTop - scrollTop, 16);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('pre-fills form fields when editing existing blog post', (
|
testWidgets('pre-fills form fields when editing existing blog post', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart';
|
import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/widgets/message_action_bottom_sheet.dart';
|
import 'package:eatvio_app/features/chat/presentation/widgets/message_action_bottom_sheet.dart';
|
||||||
@@ -8,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class _FakeAssetBundle extends CachingAssetBundle {
|
class _FakeAssetBundle extends CachingAssetBundle {
|
||||||
_FakeAssetBundle(this.assets);
|
_FakeAssetBundle(this.assets);
|
||||||
@@ -66,7 +66,9 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('AboutOrderButton renders and taps', (tester) async {
|
testWidgets('AboutOrderButton renders and taps', (tester) async {
|
||||||
var tapped = false;
|
var tapped = false;
|
||||||
await tester.pumpWidget(wrap(AboutOrderButton(onTap: () => tapped = true)));
|
await tester.pumpWidget(
|
||||||
|
wrap(AboutOrderButton(onTap: () => tapped = true)),
|
||||||
|
);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.byType(AboutOrderButton), findsOneWidget);
|
expect(find.byType(AboutOrderButton), findsOneWidget);
|
||||||
await tester.tap(find.byType(AboutOrderButton));
|
await tester.tap(find.byType(AboutOrderButton));
|
||||||
@@ -75,24 +77,44 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('MessageActionsBottomSheet renders', (tester) async {
|
testWidgets('MessageActionsBottomSheet renders', (tester) async {
|
||||||
await tester.pumpWidget(wrap(MessageActionsBottomSheet(resend: () {}, delete: () {})));
|
await tester.pumpWidget(
|
||||||
|
wrap(MessageActionsBottomSheet(resend: () {}, delete: () {})),
|
||||||
|
);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.byType(MessageActionsBottomSheet), findsOneWidget);
|
expect(find.byType(MessageActionsBottomSheet), findsOneWidget);
|
||||||
expect(find.text(S.current.resend), findsOneWidget);
|
expect(find.text(S.current.resend), findsOneWidget);
|
||||||
expect(find.text(S.current.delete), findsOneWidget);
|
expect(find.text(S.current.delete), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('NotificationBanner renders for multiple banner types', (tester) async {
|
testWidgets('NotificationBanner renders for multiple banner types', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
const SingleChildScrollView(
|
const SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
NotificationBanner(bannerType: BannerType.date, hasPersonalDiet: false),
|
NotificationBanner(
|
||||||
|
bannerType: BannerType.date,
|
||||||
|
hasPersonalDiet: false,
|
||||||
|
),
|
||||||
NotificationBanner(bannerType: BannerType.card),
|
NotificationBanner(bannerType: BannerType.card),
|
||||||
NotificationBanner(bannerType: BannerType.collectCharge, planName: 'Plan', planPrice: 100, chosenPlanId: 1),
|
NotificationBanner(
|
||||||
NotificationBanner(bannerType: BannerType.finishPlan, planName: 'Plan', planPrice: 100, chosenPlanId: 1),
|
bannerType: BannerType.collectCharge,
|
||||||
NotificationBanner(bannerType: BannerType.setPersonalDiet, hasPersonalDiet: true),
|
planName: 'Plan',
|
||||||
|
planPrice: 100,
|
||||||
|
chosenPlanId: 1,
|
||||||
|
),
|
||||||
|
NotificationBanner(
|
||||||
|
bannerType: BannerType.finishPlan,
|
||||||
|
planName: 'Plan',
|
||||||
|
planPrice: 100,
|
||||||
|
chosenPlanId: 1,
|
||||||
|
),
|
||||||
|
NotificationBanner(
|
||||||
|
bannerType: BannerType.setPersonalDiet,
|
||||||
|
hasPersonalDiet: true,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -101,5 +123,54 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.byType(NotificationBanner), findsWidgets);
|
expect(find.byType(NotificationBanner), findsWidgets);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('collect charge banner opens card settings with chosenPlanId', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
builder: (context, state) => const Scaffold(
|
||||||
|
body: NotificationBanner(
|
||||||
|
bannerType: BannerType.collectCharge,
|
||||||
|
planName: 'Plan',
|
||||||
|
planPrice: 100,
|
||||||
|
chosenPlanId: 17,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/card-settings-screen',
|
||||||
|
builder: (context, state) =>
|
||||||
|
Scaffold(body: Text('chosenPlanId:${state.extra}')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
DefaultAssetBundle(
|
||||||
|
bundle: bundle,
|
||||||
|
child: MaterialApp.router(
|
||||||
|
theme: ThemeHelper().themeData(),
|
||||||
|
localizationsDelegates: const [
|
||||||
|
S.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: const [Locale('ru')],
|
||||||
|
routerConfig: router,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.text('Получить выплату'), findsOneWidget);
|
||||||
|
await tester.tap(find.text('Получить выплату'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('chosenPlanId:17'), findsOneWidget);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,18 @@ import 'package:network_image_mock/network_image_mock.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
group('ConsultationCardWidget smoke', () {
|
group('ConsultationCardWidget smoke', () {
|
||||||
setUpAll(() async {
|
setUpAll(() async {
|
||||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
dotenv.loadFromString(
|
||||||
|
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await sl.reset();
|
await sl.reset();
|
||||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
sl.registerLazySingleton<AppConfig>(
|
||||||
|
() =>
|
||||||
|
AppConfig(buildType: AppBuildType.tests)
|
||||||
|
..setBaseUrl('http://localhost'),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
final mockConsultation = ConsultationModel(
|
final mockConsultation = ConsultationModel(
|
||||||
@@ -39,9 +45,21 @@ void main() {
|
|||||||
endDate: DateTime(2025, 1, 8),
|
endDate: DateTime(2025, 1, 8),
|
||||||
consultantId: 1,
|
consultantId: 1,
|
||||||
diaries: [
|
diaries: [
|
||||||
DiaryModel(id: 1, state: DiaryStateEnum.finished, date: DateTime(2025, 1, 1)),
|
DiaryModel(
|
||||||
DiaryModel(id: 2, state: DiaryStateEnum.commented, date: DateTime(2025, 1, 2)),
|
id: 1,
|
||||||
DiaryModel(id: 3, state: DiaryStateEnum.init, date: DateTime(2025, 1, 3)),
|
state: DiaryStateEnum.finished,
|
||||||
|
date: DateTime(2025, 1, 1),
|
||||||
|
),
|
||||||
|
DiaryModel(
|
||||||
|
id: 2,
|
||||||
|
state: DiaryStateEnum.commented,
|
||||||
|
date: DateTime(2025, 1, 2),
|
||||||
|
),
|
||||||
|
DiaryModel(
|
||||||
|
id: 3,
|
||||||
|
state: DiaryStateEnum.init,
|
||||||
|
date: DateTime(2025, 1, 3),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
personalDietId: null,
|
personalDietId: null,
|
||||||
);
|
);
|
||||||
@@ -63,18 +81,25 @@ void main() {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/profile-screen',
|
path: '/profile-screen',
|
||||||
name: 'profileScreen',
|
name: 'profileScreen',
|
||||||
builder: (context, state) => const Scaffold(body: Text('Profile')),
|
builder: (context, state) =>
|
||||||
|
const Scaffold(body: Text('Profile')),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/user-details-screen',
|
path: '/user-details-screen',
|
||||||
name: 'userDetailsScreen',
|
name: 'userDetailsScreen',
|
||||||
builder: (context, state) => const Scaffold(body: Text('User Details')),
|
builder: (context, state) =>
|
||||||
|
const Scaffold(body: Text('User Details')),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/diary-screen',
|
path: '/diary-screen',
|
||||||
name: 'diaryScreen',
|
name: 'diaryScreen',
|
||||||
builder: (context, state) => const Scaffold(body: Text('Diary')),
|
builder: (context, state) => const Scaffold(body: Text('Diary')),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/card-settings-screen',
|
||||||
|
builder: (context, state) =>
|
||||||
|
Scaffold(body: Text('chosenPlanId:${state.extra}')),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -82,7 +107,9 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('renders consultation card with user info', (tester) async {
|
testWidgets('renders consultation card with user info', (tester) async {
|
||||||
await mockNetworkImagesFor(() async {
|
await mockNetworkImagesFor(() async {
|
||||||
await tester.pumpWidget(wrap(ConsultationCardWidget(consultationsModel: mockConsultation)));
|
await tester.pumpWidget(
|
||||||
|
wrap(ConsultationCardWidget(consultationsModel: mockConsultation)),
|
||||||
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('John Doe'), findsOneWidget);
|
expect(find.text('John Doe'), findsOneWidget);
|
||||||
@@ -92,12 +119,49 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('renders diary days', (tester) async {
|
testWidgets('renders diary days', (tester) async {
|
||||||
await mockNetworkImagesFor(() async {
|
await mockNetworkImagesFor(() async {
|
||||||
await tester.pumpWidget(wrap(ConsultationCardWidget(consultationsModel: mockConsultation)));
|
await tester.pumpWidget(
|
||||||
|
wrap(ConsultationCardWidget(consultationsModel: mockConsultation)),
|
||||||
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
|
||||||
expect(find.byType(InkWell), findsWidgets);
|
expect(find.byType(InkWell), findsWidgets);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('hides collect charge when consultation is in progress', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await mockNetworkImagesFor(() async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
wrap(ConsultationCardWidget(consultationsModel: mockConsultation)),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Получить выплату'), findsNothing);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'shows collect charge and opens card settings with chosen plan id when done',
|
||||||
|
(tester) async {
|
||||||
|
final doneConsultation = mockConsultation.copyWith(
|
||||||
|
chosenPlanState: ChosenPlanStateEnum.stateDone,
|
||||||
|
);
|
||||||
|
|
||||||
|
await mockNetworkImagesFor(() async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
wrap(ConsultationCardWidget(consultationsModel: doneConsultation)),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Получить выплату'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Получить выплату'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('chosenPlanId:1'), findsOneWidget);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,5 +52,14 @@ void main() {
|
|||||||
expect(posts[2].videoEmbedUrl, contains('rutube.ru/play/embed'));
|
expect(posts[2].videoEmbedUrl, contains('rutube.ru/play/embed'));
|
||||||
expect(posts[2].author.isEntitySubscribedByMe, isNull);
|
expect(posts[2].author.isEntitySubscribedByMe, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('treats API placeholder cover as missing image', () {
|
||||||
|
expect(PostFeedMapper.resolveMediaUrl('images/element-image-placeholder.svg'), isEmpty);
|
||||||
|
expect(PostFeedMapper.hasRenderableImage('images/element-image-placeholder.svg'), isFalse);
|
||||||
|
expect(
|
||||||
|
PostFeedMapper.hasVisibleMedia(image: '', images: const [], videoEmbedUrl: null),
|
||||||
|
isFalse,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,4 +85,27 @@ void main() {
|
|||||||
expect(image.fit, BoxFit.fitWidth);
|
expect(image.fit, BoxFit.fitWidth);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('empty cover does not reserve media height', (tester) async {
|
||||||
|
await tester.pumpWidget(wrap(media(images: const [], imageUrl: '')));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(CachedNetworkImage), findsNothing);
|
||||||
|
expect(tester.getSize(find.byType(BlogPostMediaContent)).height, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('placeholder cover does not reserve media height', (tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
wrap(
|
||||||
|
media(
|
||||||
|
images: const [],
|
||||||
|
imageUrl: 'images/element-image-placeholder.svg',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(CachedNetworkImage), findsNothing);
|
||||||
|
expect(tester.getSize(find.byType(BlogPostMediaContent)).height, 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
|
||||||
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
|
import 'package:eatvio_app/features/notifications/models/notification_model.dart';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/widgets/notification_widget.dart';
|
import 'package:eatvio_app/features/notifications/presentation/widgets/notification_widget.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('NotificationWidget', () {
|
group('NotificationWidget', () {
|
||||||
@@ -21,13 +23,13 @@ void main() {
|
|||||||
supportedLocales: const [Locale('ru'), Locale('en')],
|
supportedLocales: const [Locale('ru'), Locale('en')],
|
||||||
locale: const Locale('ru'),
|
locale: const Locale('ru'),
|
||||||
theme: ThemeHelper().themeData(),
|
theme: ThemeHelper().themeData(),
|
||||||
home: Scaffold(
|
home: Scaffold(body: NotificationWidget(notification: notification)),
|
||||||
body: NotificationWidget(notification: notification),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
testWidgets('displays PlanSubscriptionStartedToAuthorNotification', (tester) async {
|
testWidgets('displays PlanSubscriptionStartedToAuthorNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = PlanSubscriptionStartedToAuthorNotification(
|
final notification = PlanSubscriptionStartedToAuthorNotification(
|
||||||
type: NotificationType.planSubscriptionStartedToAuthor,
|
type: NotificationType.planSubscriptionStartedToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -47,7 +49,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays PlanSubscriptionCanceledToAuthorNotification', (tester) async {
|
testWidgets('displays PlanSubscriptionCanceledToAuthorNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = PlanSubscriptionCanceledToAuthorNotification(
|
final notification = PlanSubscriptionCanceledToAuthorNotification(
|
||||||
type: NotificationType.planSubscriptionCanceledToAuthor,
|
type: NotificationType.planSubscriptionCanceledToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -66,7 +70,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanStartedToClientNotification', (tester) async {
|
testWidgets('displays ChosenPlanStartedToClientNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ChosenPlanStartedToClientNotification(
|
final notification = ChosenPlanStartedToClientNotification(
|
||||||
type: NotificationType.chosenPlanStartedToClient,
|
type: NotificationType.chosenPlanStartedToClient,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -83,7 +89,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanTomorrowReminderToClientNotification', (tester) async {
|
testWidgets('displays ChosenPlanTomorrowReminderToClientNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ChosenPlanTomorrowReminderToClientNotification(
|
final notification = ChosenPlanTomorrowReminderToClientNotification(
|
||||||
type: NotificationType.chosenPlanTomorrowReminderToClient,
|
type: NotificationType.chosenPlanTomorrowReminderToClient,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -131,7 +139,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays PersonalizedDietIsDefinedNotification', (tester) async {
|
testWidgets('displays PersonalizedDietIsDefinedNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = PersonalizedDietIsDefinedNotification(
|
final notification = PersonalizedDietIsDefinedNotification(
|
||||||
type: NotificationType.personalizedDietIsDefined,
|
type: NotificationType.personalizedDietIsDefined,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
@@ -144,7 +154,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanFinishedToClientNotification', (tester) async {
|
testWidgets('displays ChosenPlanFinishedToClientNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ChosenPlanFinishedToClientNotification(
|
final notification = ChosenPlanFinishedToClientNotification(
|
||||||
type: NotificationType.chosenPlanFinishedToClient,
|
type: NotificationType.chosenPlanFinishedToClient,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -163,7 +175,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays PlanSubscriptionCanceledToCustomerNotification', (tester) async {
|
testWidgets('displays PlanSubscriptionCanceledToCustomerNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = PlanSubscriptionCanceledToCustomerNotification(
|
final notification = PlanSubscriptionCanceledToCustomerNotification(
|
||||||
type: NotificationType.planSubscriptionCanceledToCustomer,
|
type: NotificationType.planSubscriptionCanceledToCustomer,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -182,7 +196,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanStartedToConsultantNotification', (tester) async {
|
testWidgets('displays ChosenPlanStartedToConsultantNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ChosenPlanStartedToConsultantNotification(
|
final notification = ChosenPlanStartedToConsultantNotification(
|
||||||
type: NotificationType.chosenPlanStartedToConsultant,
|
type: NotificationType.chosenPlanStartedToConsultant,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -201,7 +217,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanTomorrowReminderToConsultantNotification', (tester) async {
|
testWidgets('displays ChosenPlanTomorrowReminderToConsultantNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ChosenPlanTomorrowReminderToConsultantNotification(
|
final notification = ChosenPlanTomorrowReminderToConsultantNotification(
|
||||||
type: NotificationType.chosenPlanTomorrowReminderToConsultant,
|
type: NotificationType.chosenPlanTomorrowReminderToConsultant,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -217,7 +235,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays PlanSubscriptionPaidToAuthorNotification', (tester) async {
|
testWidgets('displays PlanSubscriptionPaidToAuthorNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = PlanSubscriptionPaidToAuthorNotification(
|
final notification = PlanSubscriptionPaidToAuthorNotification(
|
||||||
type: NotificationType.planSubscriptionPaidToAuthor,
|
type: NotificationType.planSubscriptionPaidToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -236,24 +256,86 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanFinishedToConsultantNotification with button', (tester) async {
|
testWidgets(
|
||||||
final notification = ChosenPlanFinishedToConsultantNotification(
|
'displays ChosenPlanFinishedToConsultantNotification with button',
|
||||||
type: NotificationType.chosenPlanFinishedToConsultant,
|
(tester) async {
|
||||||
planName: 'Test Plan',
|
final notification = ChosenPlanFinishedToConsultantNotification(
|
||||||
customerName: 'Test Customer',
|
type: NotificationType.chosenPlanFinishedToConsultant,
|
||||||
planId: 1,
|
planName: 'Test Plan',
|
||||||
chosenPlanId: 1,
|
customerName: 'Test Customer',
|
||||||
clientPhotoPath: '',
|
planId: 1,
|
||||||
clientId: 0,
|
chosenPlanId: 1,
|
||||||
isRead: false,
|
clientPhotoPath: '',
|
||||||
createdAt: testDate,
|
clientId: 0,
|
||||||
);
|
isRead: false,
|
||||||
|
createdAt: testDate,
|
||||||
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(notification));
|
await tester.pumpWidget(createTestWidget(notification));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
expect(find.text('Получить выплату'), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'collect charge from finished notification uses chosenPlanId, not planId',
|
||||||
|
(tester) async {
|
||||||
|
int? capturedChosenPlanId;
|
||||||
|
final notification = ChosenPlanFinishedToConsultantNotification(
|
||||||
|
type: NotificationType.chosenPlanFinishedToConsultant,
|
||||||
|
planName: 'Test Plan',
|
||||||
|
customerName: 'Test Customer',
|
||||||
|
planId: 99,
|
||||||
|
chosenPlanId: 42,
|
||||||
|
clientPhotoPath: '',
|
||||||
|
clientId: 0,
|
||||||
|
isRead: false,
|
||||||
|
createdAt: testDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
final router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
builder: (context, state) => Scaffold(
|
||||||
|
body: NotificationWidget(notification: notification),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: CardSettingsScreen.path,
|
||||||
|
builder: (context, state) {
|
||||||
|
capturedChosenPlanId = state.extra as int?;
|
||||||
|
return const Scaffold(body: Text('card-settings'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp.router(
|
||||||
|
localizationsDelegates: const [
|
||||||
|
S.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: const [Locale('ru'), Locale('en')],
|
||||||
|
locale: const Locale('ru'),
|
||||||
|
theme: ThemeHelper().themeData(),
|
||||||
|
routerConfig: router,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.text('Получить выплату'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('card-settings'), findsOneWidget);
|
||||||
|
expect(capturedChosenPlanId, 42);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanLastDayFinishedForUser', (tester) async {
|
testWidgets('displays ChosenPlanLastDayFinishedForUser', (tester) async {
|
||||||
final notification = ChosenPlanLastDayFinishedForUser(
|
final notification = ChosenPlanLastDayFinishedForUser(
|
||||||
@@ -587,7 +669,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays PersonalDietCreationNeededNotification', (tester) async {
|
testWidgets('displays PersonalDietCreationNeededNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = PersonalDietCreationNeededNotification(
|
final notification = PersonalDietCreationNeededNotification(
|
||||||
type: NotificationType.personalDietCreationNeeded,
|
type: NotificationType.personalDietCreationNeeded,
|
||||||
userName: 'Test Client',
|
userName: 'Test Client',
|
||||||
@@ -603,7 +687,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanClientLastDayFinishedNotification', (tester) async {
|
testWidgets('displays ChosenPlanClientLastDayFinishedNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ChosenPlanClientLastDayFinishedNotification(
|
final notification = ChosenPlanClientLastDayFinishedNotification(
|
||||||
type: NotificationType.chosenPlanClientLastDayFinished,
|
type: NotificationType.chosenPlanClientLastDayFinished,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -620,7 +706,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ChosenPlanClientLastDayStartedNotification', (tester) async {
|
testWidgets('displays ChosenPlanClientLastDayStartedNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ChosenPlanClientLastDayStartedNotification(
|
final notification = ChosenPlanClientLastDayStartedNotification(
|
||||||
type: NotificationType.chosenPlanClientLastDayStarted,
|
type: NotificationType.chosenPlanClientLastDayStarted,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -653,47 +741,58 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ClientStartPlanSubscriptionToSubscriberNotification', (tester) async {
|
testWidgets(
|
||||||
final notification = ClientStartPlanSubscriptionToSubscriberNotification(
|
'displays ClientStartPlanSubscriptionToSubscriberNotification',
|
||||||
type: NotificationType.clientStartPlanSubscriptionToSubscriber,
|
(tester) async {
|
||||||
planName: 'Test Plan',
|
final notification =
|
||||||
planId: 1,
|
ClientStartPlanSubscriptionToSubscriberNotification(
|
||||||
authorName: '',
|
type: NotificationType.clientStartPlanSubscriptionToSubscriber,
|
||||||
authorId: 0,
|
planName: 'Test Plan',
|
||||||
authorPhotoPath: '',
|
planId: 1,
|
||||||
chatUrl: '',
|
authorName: '',
|
||||||
chatId: '',
|
authorId: 0,
|
||||||
startDate: '',
|
authorPhotoPath: '',
|
||||||
isRead: false,
|
chatUrl: '',
|
||||||
createdAt: testDate,
|
chatId: '',
|
||||||
);
|
startDate: '',
|
||||||
|
isRead: false,
|
||||||
|
createdAt: testDate,
|
||||||
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(notification));
|
await tester.pumpWidget(createTestWidget(notification));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('displays ConsultantConfirmPlanSubscriptionToSubscriberNotification', (tester) async {
|
testWidgets(
|
||||||
final notification = ConsultantConfirmPlanSubscriptionToSubscriberNotification(
|
'displays ConsultantConfirmPlanSubscriptionToSubscriberNotification',
|
||||||
type: NotificationType.consultantConfirmPlanSubscriptionToSubscriber,
|
(tester) async {
|
||||||
planName: 'Test Plan',
|
final notification =
|
||||||
planId: 1,
|
ConsultantConfirmPlanSubscriptionToSubscriberNotification(
|
||||||
consultantName: 'Test Consultant',
|
type: NotificationType
|
||||||
authorId: 0,
|
.consultantConfirmPlanSubscriptionToSubscriber,
|
||||||
authorPhotoPath: '',
|
planName: 'Test Plan',
|
||||||
chatUrl: '',
|
planId: 1,
|
||||||
isRead: false,
|
consultantName: 'Test Consultant',
|
||||||
createdAt: testDate,
|
authorId: 0,
|
||||||
);
|
authorPhotoPath: '',
|
||||||
|
chatUrl: '',
|
||||||
|
isRead: false,
|
||||||
|
createdAt: testDate,
|
||||||
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(notification));
|
await tester.pumpWidget(createTestWidget(notification));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('displays ClientPaidPlanSubscriptionToAuthorNotification', (tester) async {
|
testWidgets('displays ClientPaidPlanSubscriptionToAuthorNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ClientPaidPlanSubscriptionToAuthorNotification(
|
final notification = ClientPaidPlanSubscriptionToAuthorNotification(
|
||||||
type: NotificationType.clientPaidPlanSubscriptionToAuthor,
|
type: NotificationType.clientPaidPlanSubscriptionToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -712,7 +811,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ConsultantLeftDiaryCommentNotification', (tester) async {
|
testWidgets('displays ConsultantLeftDiaryCommentNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ConsultantLeftDiaryCommentNotification(
|
final notification = ConsultantLeftDiaryCommentNotification(
|
||||||
type: NotificationType.consultantLeftDiaryComment,
|
type: NotificationType.consultantLeftDiaryComment,
|
||||||
chosenPlanId: 1,
|
chosenPlanId: 1,
|
||||||
@@ -731,26 +832,32 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ConsultantConfirmPlanSubscriptionToAuthorNotification', (tester) async {
|
testWidgets(
|
||||||
final notification = ConsultantConfirmPlanSubscriptionToAuthorNotification(
|
'displays ConsultantConfirmPlanSubscriptionToAuthorNotification',
|
||||||
type: NotificationType.consultantConfirmPlanSubscriptionToAuthor,
|
(tester) async {
|
||||||
planName: 'Test Plan',
|
final notification =
|
||||||
planId: 1,
|
ConsultantConfirmPlanSubscriptionToAuthorNotification(
|
||||||
clientName: 'Test Consultant',
|
type: NotificationType.consultantConfirmPlanSubscriptionToAuthor,
|
||||||
userId: 0,
|
planName: 'Test Plan',
|
||||||
userPhotoPath: '',
|
planId: 1,
|
||||||
chatUrl: '',
|
clientName: 'Test Consultant',
|
||||||
isRead: false,
|
userId: 0,
|
||||||
createdAt: testDate,
|
userPhotoPath: '',
|
||||||
);
|
chatUrl: '',
|
||||||
|
isRead: false,
|
||||||
|
createdAt: testDate,
|
||||||
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(notification));
|
await tester.pumpWidget(createTestWidget(notification));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('displays ClientPaidPlanSubscriptionToSubscriberNotification', (tester) async {
|
testWidgets('displays ClientPaidPlanSubscriptionToSubscriberNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ClientPaidPlanSubscriptionToSubscriberNotification(
|
final notification = ClientPaidPlanSubscriptionToSubscriberNotification(
|
||||||
type: NotificationType.clientPaidPlanSubscriptionToSubscriber,
|
type: NotificationType.clientPaidPlanSubscriptionToSubscriber,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -769,7 +876,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ClientStartPlanSubscriptionToAuthorNotification', (tester) async {
|
testWidgets('displays ClientStartPlanSubscriptionToAuthorNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ClientStartPlanSubscriptionToAuthorNotification(
|
final notification = ClientStartPlanSubscriptionToAuthorNotification(
|
||||||
type: NotificationType.clientStartPlanSubscriptionToAuthor,
|
type: NotificationType.clientStartPlanSubscriptionToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -789,7 +898,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ClientCancelPlanSubscriptionToAuthorNotification', (tester) async {
|
testWidgets('displays ClientCancelPlanSubscriptionToAuthorNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ClientCancelPlanSubscriptionToAuthorNotification(
|
final notification = ClientCancelPlanSubscriptionToAuthorNotification(
|
||||||
type: NotificationType.clientCancelPlanSubscriptionToAuthor,
|
type: NotificationType.clientCancelPlanSubscriptionToAuthor,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -808,26 +919,32 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ClientCancelPlanSubscriptionToSubscriberNotification', (tester) async {
|
testWidgets(
|
||||||
final notification = ClientCancelPlanSubscriptionToSubscriberNotification(
|
'displays ClientCancelPlanSubscriptionToSubscriberNotification',
|
||||||
type: NotificationType.clientCancelPlanSubscriptionToSubscriber,
|
(tester) async {
|
||||||
planName: 'Test Plan',
|
final notification =
|
||||||
planId: 1,
|
ClientCancelPlanSubscriptionToSubscriberNotification(
|
||||||
authorName: '',
|
type: NotificationType.clientCancelPlanSubscriptionToSubscriber,
|
||||||
authorId: 0,
|
planName: 'Test Plan',
|
||||||
authorPhotoPath: '',
|
planId: 1,
|
||||||
chatUrl: '',
|
authorName: '',
|
||||||
isRead: false,
|
authorId: 0,
|
||||||
createdAt: testDate,
|
authorPhotoPath: '',
|
||||||
);
|
chatUrl: '',
|
||||||
|
isRead: false,
|
||||||
|
createdAt: testDate,
|
||||||
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(notification));
|
await tester.pumpWidget(createTestWidget(notification));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('displays UserDailyReportCompletedNotification', (tester) async {
|
testWidgets('displays UserDailyReportCompletedNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = UserDailyReportCompletedNotification(
|
final notification = UserDailyReportCompletedNotification(
|
||||||
type: NotificationType.userDailyReportCompleted,
|
type: NotificationType.userDailyReportCompleted,
|
||||||
chosenPlanId: 1,
|
chosenPlanId: 1,
|
||||||
@@ -924,7 +1041,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ConsultantRequestStartedNotification', (tester) async {
|
testWidgets('displays ConsultantRequestStartedNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ConsultantRequestStartedNotification(
|
final notification = ConsultantRequestStartedNotification(
|
||||||
type: NotificationType.consultantRequestStarted,
|
type: NotificationType.consultantRequestStarted,
|
||||||
consultantName: 'Test Consultant',
|
consultantName: 'Test Consultant',
|
||||||
@@ -938,7 +1057,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ClientCloseComplaintSuccessNotification', (tester) async {
|
testWidgets('displays ClientCloseComplaintSuccessNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ClientCloseComplaintSuccessNotification(
|
final notification = ClientCloseComplaintSuccessNotification(
|
||||||
type: NotificationType.clientCloseComplaintSuccess,
|
type: NotificationType.clientCloseComplaintSuccess,
|
||||||
planName: 'Test Plan',
|
planName: 'Test Plan',
|
||||||
@@ -1032,7 +1153,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays ProductStatusUpdatedForAuthorNotification', (tester) async {
|
testWidgets('displays ProductStatusUpdatedForAuthorNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = ProductStatusUpdatedForAuthorNotification(
|
final notification = ProductStatusUpdatedForAuthorNotification(
|
||||||
type: NotificationType.productStatusUpdatedForAuthor,
|
type: NotificationType.productStatusUpdatedForAuthor,
|
||||||
productName: 'Test Product',
|
productName: 'Test Product',
|
||||||
@@ -1065,7 +1188,9 @@ void main() {
|
|||||||
expect(find.byType(NotificationWidget), findsOneWidget);
|
expect(find.byType(NotificationWidget), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays PersonalDietIsDefinedToCustomerNotification', (tester) async {
|
testWidgets('displays PersonalDietIsDefinedToCustomerNotification', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
final notification = PersonalDietIsDefinedToCustomerNotification(
|
final notification = PersonalDietIsDefinedToCustomerNotification(
|
||||||
type: NotificationType.personalDietIsDefinedToCustomer,
|
type: NotificationType.personalDietIsDefinedToCustomer,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
@@ -1128,35 +1253,3 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
|||||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
||||||
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.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/enums/chosen_plan_state_enum.dart';
|
||||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -29,15 +31,24 @@ void main() {
|
|||||||
late OrderInfoCubit orderInfoCubit;
|
late OrderInfoCubit orderInfoCubit;
|
||||||
|
|
||||||
setUpAll(() async {
|
setUpAll(() async {
|
||||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
dotenv.loadFromString(
|
||||||
|
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await sl.reset();
|
await sl.reset();
|
||||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
sl.registerLazySingleton<AppConfig>(
|
||||||
|
() =>
|
||||||
|
AppConfig(buildType: AppBuildType.tests)
|
||||||
|
..setBaseUrl('http://localhost'),
|
||||||
|
);
|
||||||
mockChatRepository = MockChatRepository();
|
mockChatRepository = MockChatRepository();
|
||||||
mockProfileRepository = MockProfileRepository();
|
mockProfileRepository = MockProfileRepository();
|
||||||
orderInfoCubit = OrderInfoCubit(chatRepository: mockChatRepository, profileRepository: mockProfileRepository);
|
orderInfoCubit = OrderInfoCubit(
|
||||||
|
chatRepository: mockChatRepository,
|
||||||
|
profileRepository: mockProfileRepository,
|
||||||
|
);
|
||||||
sl.registerLazySingleton<OrderInfoCubit>(() => orderInfoCubit);
|
sl.registerLazySingleton<OrderInfoCubit>(() => orderInfoCubit);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,7 +56,11 @@ void main() {
|
|||||||
orderInfoCubit.close();
|
orderInfoCubit.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget createTestWidget({required int planId, bool? successfulPayment, String? customerSlug}) {
|
Widget createTestWidget({
|
||||||
|
required int planId,
|
||||||
|
bool? successfulPayment,
|
||||||
|
String? customerSlug,
|
||||||
|
}) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: ThemeHelper().themeData(),
|
theme: ThemeHelper().themeData(),
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
@@ -55,12 +70,20 @@ void main() {
|
|||||||
GlobalCupertinoLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
],
|
],
|
||||||
supportedLocales: const [Locale('ru')],
|
supportedLocales: const [Locale('ru')],
|
||||||
home: InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment, customerSlug: customerSlug),
|
home: InfoByOrderScreen(
|
||||||
|
planId: planId,
|
||||||
|
successfulPayment: successfulPayment,
|
||||||
|
customerSlug: customerSlug,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
testWidgets('displays screen with loading state initially', (WidgetTester tester) async {
|
testWidgets('displays screen with loading state initially', (
|
||||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenThrow(Exception('Not implemented'));
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
when(
|
||||||
|
mockChatRepository.getChosenPlan(id: anyNamed('id')),
|
||||||
|
).thenThrow(Exception('Not implemented'));
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(planId: 1));
|
await tester.pumpWidget(createTestWidget(planId: 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@@ -69,8 +92,12 @@ void main() {
|
|||||||
expect(find.byType(Scaffold), findsOneWidget);
|
expect(find.byType(Scaffold), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('calls getChosenPlan when screen is created', (WidgetTester tester) async {
|
testWidgets('calls getChosenPlan when screen is created', (
|
||||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenThrow(Exception('Not implemented'));
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
when(
|
||||||
|
mockChatRepository.getChosenPlan(id: anyNamed('id')),
|
||||||
|
).thenThrow(Exception('Not implemented'));
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(planId: 123));
|
await tester.pumpWidget(createTestWidget(planId: 123));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@@ -78,18 +105,28 @@ void main() {
|
|||||||
verify(mockChatRepository.getChosenPlan(id: 123)).called(1);
|
verify(mockChatRepository.getChosenPlan(id: 123)).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('calls getChosenPlan when customerSlug is provided', (WidgetTester tester) async {
|
testWidgets('calls getChosenPlan when customerSlug is provided', (
|
||||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenThrow(Exception('Not implemented'));
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
when(
|
||||||
|
mockChatRepository.getChosenPlan(id: anyNamed('id')),
|
||||||
|
).thenThrow(Exception('Not implemented'));
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(planId: 123, customerSlug: 'test-user'));
|
await tester.pumpWidget(
|
||||||
|
createTestWidget(planId: 123, customerSlug: 'test-user'),
|
||||||
|
);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
verify(mockChatRepository.getChosenPlan(id: 123)).called(1);
|
verify(mockChatRepository.getChosenPlan(id: 123)).called(1);
|
||||||
// Note: getProfileBySlug is only called if getChosenPlan succeeds
|
// Note: getProfileBySlug is only called if getChosenPlan succeeds
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays screen with back button', (WidgetTester tester) async {
|
testWidgets('displays screen with back button', (
|
||||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenThrow(Exception('Not implemented'));
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
when(
|
||||||
|
mockChatRepository.getChosenPlan(id: anyNamed('id')),
|
||||||
|
).thenThrow(Exception('Not implemented'));
|
||||||
|
|
||||||
await tester.pumpWidget(createTestWidget(planId: 1));
|
await tester.pumpWidget(createTestWidget(planId: 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@@ -98,7 +135,9 @@ void main() {
|
|||||||
expect(find.byIcon(Icons.arrow_back), findsOneWidget);
|
expect(find.byIcon(Icons.arrow_back), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays screen with loaded plan', (WidgetTester tester) async {
|
testWidgets('displays screen with loaded plan', (
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
final chosenPlan = ChosenPlanModel(
|
final chosenPlan = ChosenPlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
optionType: 'options1',
|
optionType: 'options1',
|
||||||
@@ -113,10 +152,20 @@ void main() {
|
|||||||
image: 'https://example.com/image.jpg',
|
image: 'https://example.com/image.jpg',
|
||||||
duration: 30,
|
duration: 30,
|
||||||
state: 'active',
|
state: 'active',
|
||||||
type: PlanType(fit: true, slim: false, behavior: false, wellness: false),
|
type: PlanType(
|
||||||
|
fit: true,
|
||||||
|
slim: false,
|
||||||
|
behavior: false,
|
||||||
|
wellness: false,
|
||||||
|
),
|
||||||
details: 'Test details',
|
details: 'Test details',
|
||||||
packetsCount: 3,
|
packetsCount: 3,
|
||||||
author: Author(authorSlug: 'author', authorName: 'Author Name', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false),
|
author: Author(
|
||||||
|
authorSlug: 'author',
|
||||||
|
authorName: 'Author Name',
|
||||||
|
authorProfileIcon: 'https://example.com/icon.jpg',
|
||||||
|
authorIsMe: false,
|
||||||
|
),
|
||||||
totalLikes: 10,
|
totalLikes: 10,
|
||||||
totalViews: 100,
|
totalViews: 100,
|
||||||
totalComments: 5,
|
totalComments: 5,
|
||||||
@@ -127,7 +176,9 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenAnswer((_) async => chosenPlan);
|
when(
|
||||||
|
mockChatRepository.getChosenPlan(id: anyNamed('id')),
|
||||||
|
).thenAnswer((_) async => chosenPlan);
|
||||||
|
|
||||||
await mockNetworkImagesFor(() async {
|
await mockNetworkImagesFor(() async {
|
||||||
await tester.pumpWidget(createTestWidget(planId: 1));
|
await tester.pumpWidget(createTestWidget(planId: 1));
|
||||||
@@ -137,7 +188,9 @@ void main() {
|
|||||||
expect(find.byType(InfoByOrderScreen), findsOneWidget);
|
expect(find.byType(InfoByOrderScreen), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('displays screen with successful payment', (WidgetTester tester) async {
|
testWidgets('displays screen with successful payment', (
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
final chosenPlan = ChosenPlanModel(
|
final chosenPlan = ChosenPlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
optionType: 'options1',
|
optionType: 'options1',
|
||||||
@@ -152,10 +205,20 @@ void main() {
|
|||||||
image: 'https://example.com/image.jpg',
|
image: 'https://example.com/image.jpg',
|
||||||
duration: 30,
|
duration: 30,
|
||||||
state: 'active',
|
state: 'active',
|
||||||
type: PlanType(fit: true, slim: false, behavior: false, wellness: false),
|
type: PlanType(
|
||||||
|
fit: true,
|
||||||
|
slim: false,
|
||||||
|
behavior: false,
|
||||||
|
wellness: false,
|
||||||
|
),
|
||||||
details: 'Test details',
|
details: 'Test details',
|
||||||
packetsCount: 3,
|
packetsCount: 3,
|
||||||
author: Author(authorSlug: 'author', authorName: 'Author Name', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false),
|
author: Author(
|
||||||
|
authorSlug: 'author',
|
||||||
|
authorName: 'Author Name',
|
||||||
|
authorProfileIcon: 'https://example.com/icon.jpg',
|
||||||
|
authorIsMe: false,
|
||||||
|
),
|
||||||
totalLikes: 10,
|
totalLikes: 10,
|
||||||
totalViews: 100,
|
totalViews: 100,
|
||||||
totalComments: 5,
|
totalComments: 5,
|
||||||
@@ -166,14 +229,114 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
when(mockChatRepository.getChosenPlan(id: anyNamed('id'))).thenAnswer((_) async => chosenPlan);
|
when(
|
||||||
|
mockChatRepository.getChosenPlan(id: anyNamed('id')),
|
||||||
|
).thenAnswer((_) async => chosenPlan);
|
||||||
|
|
||||||
await mockNetworkImagesFor(() async {
|
await mockNetworkImagesFor(() async {
|
||||||
await tester.pumpWidget(createTestWidget(planId: 1, successfulPayment: true));
|
await tester.pumpWidget(
|
||||||
|
createTestWidget(planId: 1, successfulPayment: true),
|
||||||
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(find.byType(InfoByOrderScreen), findsOneWidget);
|
expect(find.byType(InfoByOrderScreen), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ChosenPlanModel donePlan() {
|
||||||
|
return ChosenPlanModel(
|
||||||
|
id: 1,
|
||||||
|
optionType: 'options1',
|
||||||
|
startDate: DateTime(2024, 1, 1),
|
||||||
|
chosenPlanState: ChosenPlanStateEnum.stateDone,
|
||||||
|
chatId: 'chat-1',
|
||||||
|
consultantId: 1,
|
||||||
|
plan: const PlanModel(
|
||||||
|
id: 1,
|
||||||
|
slug: 'test-plan',
|
||||||
|
name: 'Test Plan',
|
||||||
|
image: 'https://example.com/image.jpg',
|
||||||
|
duration: 30,
|
||||||
|
state: 'active',
|
||||||
|
type: PlanType(
|
||||||
|
fit: true,
|
||||||
|
slim: false,
|
||||||
|
behavior: false,
|
||||||
|
wellness: false,
|
||||||
|
),
|
||||||
|
details: 'Test details',
|
||||||
|
packetsCount: 3,
|
||||||
|
author: Author(
|
||||||
|
authorSlug: 'author',
|
||||||
|
authorName: 'Author Name',
|
||||||
|
authorProfileIcon: 'https://example.com/icon.jpg',
|
||||||
|
authorIsMe: false,
|
||||||
|
),
|
||||||
|
totalLikes: 10,
|
||||||
|
totalViews: 100,
|
||||||
|
totalComments: 5,
|
||||||
|
isFavorite: false,
|
||||||
|
minPriceOfOptions: 1000,
|
||||||
|
maxPriceOfOptions: 2000,
|
||||||
|
wasLikedByMe: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'shows collect charge banner for consultant when plan is done',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
const profile = ProfileModel(
|
||||||
|
id: 2,
|
||||||
|
name: 'Customer',
|
||||||
|
email: 'customer@example.com',
|
||||||
|
phoneConfirmationState: 'confirmed',
|
||||||
|
isPublicProfile: true,
|
||||||
|
isBlockedByMe: false,
|
||||||
|
isBlockedByAuthor: false,
|
||||||
|
isBlockedByUser: false,
|
||||||
|
isEntitySubscribedByMe: false,
|
||||||
|
slug: 'test-user',
|
||||||
|
profilePhotoUrl: '/test.jpg',
|
||||||
|
roles: ['user'],
|
||||||
|
badges: [],
|
||||||
|
subscriptions: [],
|
||||||
|
subscribers: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
when(
|
||||||
|
mockChatRepository.getChosenPlan(id: anyNamed('id')),
|
||||||
|
).thenAnswer((_) async => donePlan());
|
||||||
|
when(
|
||||||
|
mockProfileRepository.getProfileBySlug('test-user'),
|
||||||
|
).thenAnswer((_) async => profile);
|
||||||
|
|
||||||
|
await mockNetworkImagesFor(() async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
createTestWidget(planId: 1, customerSlug: 'test-user'),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(find.byType(CollectChargeBanner), findsOneWidget);
|
||||||
|
expect(find.text('Получить выплату'), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('hides collect charge banner for client when plan is done', (
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
when(
|
||||||
|
mockChatRepository.getChosenPlan(id: anyNamed('id')),
|
||||||
|
).thenAnswer((_) async => donePlan());
|
||||||
|
|
||||||
|
await mockNetworkImagesFor(() async {
|
||||||
|
await tester.pumpWidget(createTestWidget(planId: 1));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(find.byType(CollectChargeBanner), findsNothing);
|
||||||
|
expect(find.text('Получить выплату'), findsNothing);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ 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/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/enums/chosen_plan_state_enum.dart';
|
||||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.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/plan_pagination_model.dart'
|
||||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart' show Author;
|
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:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
@@ -20,8 +22,14 @@ void main() {
|
|||||||
group('CollectChargeBanner', () {
|
group('CollectChargeBanner', () {
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await sl.reset();
|
await sl.reset();
|
||||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
dotenv.loadFromString(
|
||||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
|
||||||
|
);
|
||||||
|
sl.registerLazySingleton<AppConfig>(
|
||||||
|
() =>
|
||||||
|
AppConfig(buildType: AppBuildType.tests)
|
||||||
|
..setBaseUrl('http://localhost'),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget createTestWidget(Widget child) {
|
Widget createTestWidget(Widget child) {
|
||||||
@@ -42,12 +50,76 @@ void main() {
|
|||||||
child: Scaffold(body: SingleChildScrollView(child: child)),
|
child: Scaffold(body: SingleChildScrollView(child: child)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/card-settings-screen',
|
||||||
|
builder: (context, state) =>
|
||||||
|
Scaffold(body: Text('chosenPlanId:${state.extra}')),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
testWidgets('should display collect charge banner with plan name and price', (WidgetTester tester) async {
|
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
|
// Arrange
|
||||||
final chosenPlan = ChosenPlanModel(
|
final chosenPlan = ChosenPlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -63,10 +135,20 @@ void main() {
|
|||||||
image: 'test.jpg',
|
image: 'test.jpg',
|
||||||
duration: 30,
|
duration: 30,
|
||||||
state: 'active',
|
state: 'active',
|
||||||
type: PlanType(fit: true, slim: false, behavior: false, wellness: false),
|
type: PlanType(
|
||||||
|
fit: true,
|
||||||
|
slim: false,
|
||||||
|
behavior: false,
|
||||||
|
wellness: false,
|
||||||
|
),
|
||||||
details: 'Test details',
|
details: 'Test details',
|
||||||
packetsCount: 1,
|
packetsCount: 1,
|
||||||
author: Author(authorSlug: '', authorName: '', authorProfileIcon: '', authorIsMe: false),
|
author: Author(
|
||||||
|
authorSlug: '',
|
||||||
|
authorName: '',
|
||||||
|
authorProfileIcon: '',
|
||||||
|
authorIsMe: false,
|
||||||
|
),
|
||||||
totalLikes: 0,
|
totalLikes: 0,
|
||||||
totalViews: 0,
|
totalViews: 0,
|
||||||
totalComments: 0,
|
totalComments: 0,
|
||||||
@@ -83,50 +165,10 @@ void main() {
|
|||||||
await tester.pumpWidget(createTestWidget(widget));
|
await tester.pumpWidget(createTestWidget(widget));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Assert
|
await tester.tap(find.byType(CustomOutlinedButton));
|
||||||
expect(find.byType(CollectChargeBanner), findsOneWidget);
|
await tester.pumpAndSettle();
|
||||||
expect(find.byType(CustomOutlinedButton), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('should have button that navigates to card settings', (WidgetTester tester) async {
|
expect(find.text('chosenPlanId:1'), findsOneWidget);
|
||||||
// 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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||||
@@ -6,22 +7,36 @@ import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
|||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
||||||
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/models/chosen_diets_pagination_model.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:mockito/annotations.dart';
|
import 'package:mockito/annotations.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
import 'about_plan_cubit_test.mocks.dart';
|
import 'about_plan_cubit_test.mocks.dart';
|
||||||
|
|
||||||
@GenerateNiceMocks([MockSpec<PlanRepository>()])
|
@GenerateNiceMocks([MockSpec<PlanRepository>(), MockSpec<ProfileRepository>(), MockSpec<DietRepository>()])
|
||||||
void main() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
group('AboutPlanCubit', () {
|
group('AboutPlanCubit', () {
|
||||||
AboutPlanCubit? aboutPlanCubit;
|
AboutPlanCubit? aboutPlanCubit;
|
||||||
late MockPlanRepository mockPlanRepository;
|
late MockPlanRepository mockPlanRepository;
|
||||||
|
late MockProfileRepository mockProfileRepository;
|
||||||
|
late MockDietRepository mockDietRepository;
|
||||||
|
|
||||||
|
AboutPlanCubit createCubit() {
|
||||||
|
return AboutPlanCubit(
|
||||||
|
planRepository: mockPlanRepository,
|
||||||
|
profileRepository: mockProfileRepository,
|
||||||
|
dietRepository: mockDietRepository,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
mockPlanRepository = MockPlanRepository();
|
mockPlanRepository = MockPlanRepository();
|
||||||
|
mockProfileRepository = MockProfileRepository();
|
||||||
|
mockDietRepository = MockDietRepository();
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
@@ -32,7 +47,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('начальное состояние - initial', () {
|
test('начальное состояние - initial', () {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
expect(aboutPlanCubit!.state, isA<AboutPlanState>());
|
expect(aboutPlanCubit!.state, isA<AboutPlanState>());
|
||||||
expect(aboutPlanCubit!.state.isLoading, isFalse);
|
expect(aboutPlanCubit!.state.isLoading, isFalse);
|
||||||
@@ -40,7 +55,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('getPlan - успешная загрузка плана', () async {
|
test('getPlan - успешная загрузка плана', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -79,7 +94,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('getPlanWithLoading - устанавливает loading состояние', () async {
|
test('getPlanWithLoading - устанавливает loading состояние', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -117,7 +132,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('choseOption - изменяет выбранную опцию', () {
|
test('choseOption - изменяет выбранную опцию', () {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
aboutPlanCubit!.choseOption(2);
|
aboutPlanCubit!.choseOption(2);
|
||||||
|
|
||||||
@@ -125,7 +140,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('likePlan - лайк плана', () async {
|
test('likePlan - лайк плана', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -166,7 +181,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('getPlan - обработка ошибки', () async {
|
test('getPlan - обработка ошибки', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
when(mockPlanRepository.getPlan('test-plan')).thenThrow(Exception('Ошибка загрузки'));
|
when(mockPlanRepository.getPlan('test-plan')).thenThrow(Exception('Ошибка загрузки'));
|
||||||
|
|
||||||
@@ -177,7 +192,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('getPlanWithLoading - обработка ошибки', () async {
|
test('getPlanWithLoading - обработка ошибки', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
when(mockPlanRepository.getPlan('test-plan')).thenThrow(Exception('Ошибка загрузки'));
|
when(mockPlanRepository.getPlan('test-plan')).thenThrow(Exception('Ошибка загрузки'));
|
||||||
|
|
||||||
@@ -188,7 +203,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('likePlan - убирает лайк если уже лайкнуто', () async {
|
test('likePlan - убирает лайк если уже лайкнуто', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -231,7 +246,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('addToFavorite - добавляет план в избранное', () async {
|
test('addToFavorite - добавляет план в избранное', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -273,7 +288,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('removeFromFavorite - удаляет план из избранного', () async {
|
test('removeFromFavorite - удаляет план из избранного', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -315,7 +330,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('chosePlan - успешно выбирает план', () async {
|
test('chosePlan - успешно выбирает план', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -368,7 +383,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('chosePlan - возвращает ошибку hasPlan при 400 статусе', () async {
|
test('chosePlan - возвращает ошибку hasPlan при 400 статусе', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -415,7 +430,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('likePlan - лайк плана с index', () async {
|
test('likePlan - лайк плана с index', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -458,7 +473,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('likePlan - убирает лайк с index', () async {
|
test('likePlan - убирает лайк с index', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -501,7 +516,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('likePlan - обрабатывает ошибку', () async {
|
test('likePlan - обрабатывает ошибку', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -542,7 +557,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('likePlan - не делает ничего если план не загружен', () async {
|
test('likePlan - не делает ничего если план не загружен', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
await aboutPlanCubit!.likePlan(index: null, slug: 'test-plan');
|
await aboutPlanCubit!.likePlan(index: null, slug: 'test-plan');
|
||||||
|
|
||||||
@@ -551,7 +566,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('addToFavorite - добавляет план в избранное с index', () async {
|
test('addToFavorite - добавляет план в избранное с index', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -592,7 +607,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('addToFavorite - обрабатывает ошибку', () async {
|
test('addToFavorite - обрабатывает ошибку', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -632,7 +647,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('addToFavorite - не делает ничего если план не загружен', () async {
|
test('addToFavorite - не делает ничего если план не загружен', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
await aboutPlanCubit!.addToFavorite(slug: 'test-plan', index: null);
|
await aboutPlanCubit!.addToFavorite(slug: 'test-plan', index: null);
|
||||||
|
|
||||||
@@ -641,7 +656,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('removeFromFavorite - удаляет план из избранного с index', () async {
|
test('removeFromFavorite - удаляет план из избранного с index', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -682,7 +697,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('removeFromFavorite - обрабатывает ошибку', () async {
|
test('removeFromFavorite - обрабатывает ошибку', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -722,7 +737,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('removeFromFavorite - не делает ничего если план не загружен', () async {
|
test('removeFromFavorite - не делает ничего если план не загружен', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
await aboutPlanCubit!.removeFromFavorite(slug: 'test-plan', index: null);
|
await aboutPlanCubit!.removeFromFavorite(slug: 'test-plan', index: null);
|
||||||
|
|
||||||
@@ -731,7 +746,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('chosePlan - выбирает план с optionIndex 1', () async {
|
test('chosePlan - выбирает план с optionIndex 1', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -784,7 +799,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('chosePlan - выбирает план с optionIndex 2', () async {
|
test('chosePlan - выбирает план с optionIndex 2', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -837,7 +852,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('chosePlan - возвращает пустой результат когда slug == null', () async {
|
test('chosePlan - возвращает пустой результат когда slug == null', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
final result = await aboutPlanCubit!.chosePlan();
|
final result = await aboutPlanCubit!.chosePlan();
|
||||||
|
|
||||||
@@ -847,7 +862,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('chosePlan - обрабатывает DioException с другим статусом', () async {
|
test('chosePlan - обрабатывает DioException с другим статусом', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -894,7 +909,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('chosePlan - обрабатывает общее исключение', () async {
|
test('chosePlan - обрабатывает общее исключение', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -936,7 +951,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('getPlanWithLoading - устанавливает isLoading в true перед загрузкой', () async {
|
test('getPlanWithLoading - устанавливает isLoading в true перед загрузкой', () async {
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
aboutPlanCubit = createCubit();
|
||||||
|
|
||||||
const testPlan = PlanModel(
|
const testPlan = PlanModel(
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -977,5 +992,76 @@ void main() {
|
|||||||
await future;
|
await future;
|
||||||
expect(aboutPlanCubit!.state.isLoading, isFalse);
|
expect(aboutPlanCubit!.state.isLoading, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('getActivePublicDiet - возвращает активную публичную диету', () async {
|
||||||
|
aboutPlanCubit = createCubit();
|
||||||
|
final publicDiet = ChosenDietModel(
|
||||||
|
chosenDietID: 1,
|
||||||
|
chosenPlanId: 0,
|
||||||
|
state: 'started',
|
||||||
|
startDate: DateTime(2024, 1, 1),
|
||||||
|
dietId: 10,
|
||||||
|
);
|
||||||
|
when(mockProfileRepository.getChosenDiets()).thenAnswer((_) async => [publicDiet]);
|
||||||
|
|
||||||
|
final result = await aboutPlanCubit!.getActivePublicDiet();
|
||||||
|
|
||||||
|
expect(result, isNotNull);
|
||||||
|
expect(result!.dietId, 10);
|
||||||
|
expect(result.chosenPlanId, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getActivePublicDiet - игнорирует диету, привязанную к плану', () async {
|
||||||
|
aboutPlanCubit = createCubit();
|
||||||
|
final personalDiet = ChosenDietModel(
|
||||||
|
chosenDietID: 1,
|
||||||
|
chosenPlanId: 5,
|
||||||
|
state: 'started',
|
||||||
|
startDate: DateTime(2024, 1, 1),
|
||||||
|
dietId: 10,
|
||||||
|
);
|
||||||
|
when(mockProfileRepository.getChosenDiets()).thenAnswer((_) async => [personalDiet]);
|
||||||
|
|
||||||
|
final result = await aboutPlanCubit!.getActivePublicDiet();
|
||||||
|
|
||||||
|
expect(result, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getActivePublicDiet - возвращает null если нет выбранных диет', () async {
|
||||||
|
aboutPlanCubit = createCubit();
|
||||||
|
when(mockProfileRepository.getChosenDiets()).thenAnswer((_) async => []);
|
||||||
|
|
||||||
|
final result = await aboutPlanCubit!.getActivePublicDiet();
|
||||||
|
|
||||||
|
expect(result, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getActivePublicDiet - возвращает null при ошибке', () async {
|
||||||
|
aboutPlanCubit = createCubit();
|
||||||
|
when(mockProfileRepository.getChosenDiets()).thenThrow(Exception('error'));
|
||||||
|
|
||||||
|
final result = await aboutPlanCubit!.getActivePublicDiet();
|
||||||
|
|
||||||
|
expect(result, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancelPublicDiet - успешно отменяет диету', () async {
|
||||||
|
aboutPlanCubit = createCubit();
|
||||||
|
when(mockDietRepository.cancelDiet(10)).thenAnswer((_) async {});
|
||||||
|
|
||||||
|
final result = await aboutPlanCubit!.cancelPublicDiet(10);
|
||||||
|
|
||||||
|
expect(result, isTrue);
|
||||||
|
verify(mockDietRepository.cancelDiet(10)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancelPublicDiet - возвращает false при ошибке', () async {
|
||||||
|
aboutPlanCubit = createCubit();
|
||||||
|
when(mockDietRepository.cancelDiet(10)).thenThrow(Exception('error'));
|
||||||
|
|
||||||
|
final result = await aboutPlanCubit!.cancelPublicDiet(10);
|
||||||
|
|
||||||
|
expect(result, isFalse);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit
|
|||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/presentation/widgets/confirm_start_plan_bottom_sheet.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/models/chosen_diets_pagination_model.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -301,5 +303,82 @@ void main() {
|
|||||||
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
expect(find.byType(AboutPlanScreen), findsOneWidget);
|
||||||
expect(find.text('Test Plan'), findsOneWidget);
|
expect(find.text('Test Plan'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('order with public diet shows start plan confirmation', (tester) async {
|
||||||
|
final planCubit = MockAboutPlanCubit();
|
||||||
|
final feedCubit = MockFeedCubit();
|
||||||
|
final commentsCubit = MockCommentsCubit();
|
||||||
|
const state = AboutPlanState(isLoading: false, planModel: testPlan, chosenOptionIndex: 0, error: null);
|
||||||
|
final publicDiet = ChosenDietModel(
|
||||||
|
chosenDietID: 1,
|
||||||
|
chosenPlanId: 0,
|
||||||
|
state: 'started',
|
||||||
|
startDate: DateTime(2024, 1, 1),
|
||||||
|
dietId: 10,
|
||||||
|
);
|
||||||
|
|
||||||
|
when(planCubit.state).thenReturn(state);
|
||||||
|
when(planCubit.stream).thenAnswer((_) => const Stream<AboutPlanState>.empty());
|
||||||
|
when(planCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||||
|
when(planCubit.getActivePublicDiet()).thenAnswer((_) async => publicDiet);
|
||||||
|
when(feedCubit.viewPlan(any)).thenAnswer((_) async => Future<void>.value());
|
||||||
|
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||||
|
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||||
|
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||||
|
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||||
|
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||||
|
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||||
|
|
||||||
|
registerMocks(planCubit, commentsCubit);
|
||||||
|
|
||||||
|
await mockNetworkImagesFor(() async {
|
||||||
|
await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null, highlightedCommentId: null)));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump();
|
||||||
|
});
|
||||||
|
|
||||||
|
await tester.tap(find.text('Заказать'));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(ConfirmStartPlanBottomSheet), findsOneWidget);
|
||||||
|
expect(find.text('Вы уверены, что хотите начать прохождение плана?'), findsOneWidget);
|
||||||
|
verifyNever(planCubit.chosePlan());
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('order without public diet calls chosePlan', (tester) async {
|
||||||
|
final planCubit = MockAboutPlanCubit();
|
||||||
|
final feedCubit = MockFeedCubit();
|
||||||
|
final commentsCubit = MockCommentsCubit();
|
||||||
|
const state = AboutPlanState(isLoading: false, planModel: testPlan, chosenOptionIndex: 0, error: null);
|
||||||
|
|
||||||
|
when(planCubit.state).thenReturn(state);
|
||||||
|
when(planCubit.stream).thenAnswer((_) => const Stream<AboutPlanState>.empty());
|
||||||
|
when(planCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||||
|
when(planCubit.getActivePublicDiet()).thenAnswer((_) async => null);
|
||||||
|
when(planCubit.chosePlan()).thenAnswer((_) async => (null, ''));
|
||||||
|
when(feedCubit.viewPlan(any)).thenAnswer((_) async => Future<void>.value());
|
||||||
|
when(feedCubit.stream).thenAnswer((_) => const Stream.empty());
|
||||||
|
when(feedCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||||
|
when(commentsCubit.state).thenReturn(const CommentsState());
|
||||||
|
when(commentsCubit.stream).thenAnswer((_) => const Stream<CommentsState>.empty());
|
||||||
|
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||||
|
when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future<void>.value());
|
||||||
|
|
||||||
|
registerMocks(planCubit, commentsCubit);
|
||||||
|
|
||||||
|
await mockNetworkImagesFor(() async {
|
||||||
|
await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null, highlightedCommentId: null)));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump();
|
||||||
|
});
|
||||||
|
|
||||||
|
await tester.tap(find.text('Заказать'));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(ConfirmStartPlanBottomSheet), findsNothing);
|
||||||
|
verify(planCubit.chosePlan()).called(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/presentation/widgets/confirm_start_plan_bottom_sheet.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';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ConfirmStartPlanBottomSheet', () {
|
||||||
|
Widget createTestWidget(Widget child) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeHelper().themeData(),
|
||||||
|
localizationsDelegates: const [
|
||||||
|
S.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: const [Locale('ru')],
|
||||||
|
home: Scaffold(body: child),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('показывает текст подтверждения начала плана', (tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
createTestWidget(
|
||||||
|
ConfirmStartPlanBottomSheet(onConfirm: () async {}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.text('Вы уверены, что хотите начать прохождение плана?'), findsOneWidget);
|
||||||
|
expect(find.byType(CustomOutlinedButton), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('по нажатию подтверждения вызывает onConfirm', (tester) async {
|
||||||
|
var confirmed = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
createTestWidget(
|
||||||
|
ConfirmStartPlanBottomSheet(
|
||||||
|
onConfirm: () async {
|
||||||
|
confirmed = true;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
await tester.tap(find.byType(CustomOutlinedButton));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(confirmed, isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@ import 'package:eatvio_app/core/app_initializer.dart';
|
|||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/widgets/animated_option_button.dart';
|
import 'package:eatvio_app/core/widgets/animated_option_button.dart';
|
||||||
|
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/widgets/options_tab.dart';
|
import 'package:eatvio_app/features/plans/presentation/widgets/options_tab.dart';
|
||||||
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -18,17 +20,25 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|||||||
|
|
||||||
import 'widgets_test.mocks.dart';
|
import 'widgets_test.mocks.dart';
|
||||||
|
|
||||||
@GenerateNiceMocks([MockSpec<PlanRepository>()])
|
@GenerateNiceMocks([MockSpec<PlanRepository>(), MockSpec<ProfileRepository>(), MockSpec<DietRepository>()])
|
||||||
void main() {
|
void main() {
|
||||||
group('Plans Widgets Tests', () {
|
group('Plans Widgets Tests', () {
|
||||||
late MockPlanRepository mockPlanRepository;
|
late MockPlanRepository mockPlanRepository;
|
||||||
|
late MockProfileRepository mockProfileRepository;
|
||||||
|
late MockDietRepository mockDietRepository;
|
||||||
late AboutPlanCubit aboutPlanCubit;
|
late AboutPlanCubit aboutPlanCubit;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await sl.reset();
|
await sl.reset();
|
||||||
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
|
||||||
mockPlanRepository = MockPlanRepository();
|
mockPlanRepository = MockPlanRepository();
|
||||||
aboutPlanCubit = AboutPlanCubit(planRepository: mockPlanRepository);
|
mockProfileRepository = MockProfileRepository();
|
||||||
|
mockDietRepository = MockDietRepository();
|
||||||
|
aboutPlanCubit = AboutPlanCubit(
|
||||||
|
planRepository: mockPlanRepository,
|
||||||
|
profileRepository: mockProfileRepository,
|
||||||
|
dietRepository: mockDietRepository,
|
||||||
|
);
|
||||||
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,22 @@ void main() {
|
|||||||
expect(model.dietId, 5);
|
expect(model.dietId, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('creates ChosenDietModel from JSON with null chosen_plan_id as public diet', () {
|
||||||
|
final json = {
|
||||||
|
'id': 1,
|
||||||
|
'chosen_plan_id': null,
|
||||||
|
'state': 'started',
|
||||||
|
'start_date': '2023-01-01T00:00:00Z',
|
||||||
|
'diet': {'id': 5},
|
||||||
|
};
|
||||||
|
|
||||||
|
final model = ChosenDietModel.fromJson(json);
|
||||||
|
|
||||||
|
expect(model.chosenDietID, 1);
|
||||||
|
expect(model.chosenPlanId, 0);
|
||||||
|
expect(model.dietId, 5);
|
||||||
|
});
|
||||||
|
|
||||||
test('creates ChosenDietModel with null dietId', () {
|
test('creates ChosenDietModel with null dietId', () {
|
||||||
final model = ChosenDietModel(
|
final model = ChosenDietModel(
|
||||||
chosenDietID: 1,
|
chosenDietID: 1,
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import 'package:eatvio_app/features/profile/presentation/widgets/expandable_page_view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ExpandablePageView', () {
|
||||||
|
testWidgets(
|
||||||
|
'sizes itself to a child taller than the old fixed 781 height',
|
||||||
|
(tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: ExpandablePageView(
|
||||||
|
itemCount: 1,
|
||||||
|
itemBuilder: _tallPlanCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
expect(find.text('Питание для диабетиков'), findsOneWidget);
|
||||||
|
expect(find.text('Показать ещё'), findsOneWidget);
|
||||||
|
|
||||||
|
final viewSize = tester.getSize(find.byType(ExpandablePageView));
|
||||||
|
expect(viewSize.height, greaterThan(781));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('does not overflow a vertical Column inside a bounded parent', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: SizedBox(
|
||||||
|
height: 781,
|
||||||
|
width: 402,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: ExpandablePageView(
|
||||||
|
itemCount: 1,
|
||||||
|
itemBuilder: _tallPlanCard,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
expect(find.text('Показать ещё'), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tallPlanCard(BuildContext context, int index) {
|
||||||
|
return const Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 402, height: 426),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Text('Питание для диабетиков'),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
SizedBox(width: 370, height: 156),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 16, top: 8),
|
||||||
|
child: Text('Показать ещё'),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
SizedBox(height: 72),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
SizedBox(height: 32),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -257,6 +257,8 @@ void main() {
|
|||||||
|
|
||||||
expect(find.byType(BlogPostMediaContent), findsOneWidget);
|
expect(find.byType(BlogPostMediaContent), findsOneWidget);
|
||||||
expect(find.textContaining('Галерея и видео'), findsWidgets);
|
expect(find.textContaining('Галерея и видео'), findsWidgets);
|
||||||
|
expect(find.byType(CircleAvatar), findsNothing);
|
||||||
|
expect(find.text(post.author.authorName), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('renders gallery-only media content', (tester) async {
|
testWidgets('renders gallery-only media content', (tester) async {
|
||||||
|
|||||||
Reference in New Issue
Block a user