feat(web_v2): Add Buy Pack Button to PackDetailsPage
Task ID: WEB-003 Priority: high Changes: Completed by: AI Agent Duration: 839723ms
This commit is contained in:
parent
108abfd133
commit
d0b939375e
4 changed files with 488 additions and 41 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"component": "web_v2",
|
"component": "web_v2",
|
||||||
"current_task_id": "WEB-001",
|
"current_task_id": "WEB-003",
|
||||||
"iteration_count": 2,
|
"iteration_count": 3,
|
||||||
"max_iterations": 10,
|
"max_iterations": 10,
|
||||||
"started_at": "2025-11-21T00:31:28.302997+00:00",
|
"started_at": "2025-11-21T00:31:28.302997+00:00",
|
||||||
"last_commit": null,
|
"last_commit": null,
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
"id": "WEB-003",
|
"id": "WEB-003",
|
||||||
"title": "Add Buy Pack Button to PackDetailsPage",
|
"title": "Add Buy Pack Button to PackDetailsPage",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"status": "pending",
|
"status": "in_progress",
|
||||||
"estimated_hours": 2.0,
|
"estimated_hours": 2.0,
|
||||||
"description": "Add a 'Buy Pack' button to PackDetailsPage that navigates to PurchasePage when a pack requires purchase. This completes the pack purchase flow integration.",
|
"description": "Add a 'Buy Pack' button to PackDetailsPage that navigates to PurchasePage when a pack requires purchase. This completes the pack purchase flow integration.",
|
||||||
"acceptance_criteria": [
|
"acceptance_criteria": [
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
||||||
double _shuffleAnimationTurns = 0;
|
double _shuffleAnimationTurns = 0;
|
||||||
Map<int, int> _previousCardIndexById = {};
|
Map<int, int> _previousCardIndexById = {};
|
||||||
int _lastAnimatedShuffleKey = 0;
|
int _lastAnimatedShuffleKey = 0;
|
||||||
|
bool _isNavigatingToPurchase = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -78,14 +79,6 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
||||||
final packResponse = await appScope.httpRepository.getPack(widget.packId);
|
final packResponse = await appScope.httpRepository.getPack(widget.packId);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// Check if pack needs to be purchased
|
|
||||||
if (packResponse.responseType == GetCardPackResponseType.buy) {
|
|
||||||
log('Pack requires purchase, redirecting to purchase page', name: 'PackDetailsPage');
|
|
||||||
// Navigate to purchase page
|
|
||||||
context.replace('/purchase/${widget.packId}');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_packResponse = packResponse;
|
_packResponse = packResponse;
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
|
|
@ -222,7 +215,7 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
||||||
return _buildErrorState();
|
return _buildErrorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_packResponse == null || _packResponse is! CardPackDto) {
|
if (_packResponse == null) {
|
||||||
return const Center(child: Text('Pack not found'));
|
return const Center(child: Text('Pack not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,10 +253,109 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the pack requires purchase
|
||||||
|
bool _requiresPurchase() {
|
||||||
|
return _packResponse?.responseType == GetCardPackResponseType.buy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a CardPackDto from CardPackBuyDto for display purposes
|
||||||
|
CardPackDto _createDisplayPackFromBuyDto(CardPackBuyDto buyDto) {
|
||||||
|
return CardPackDto(
|
||||||
|
id: buyDto.id,
|
||||||
|
title: buyDto.title,
|
||||||
|
subtitle: buyDto.subtitle,
|
||||||
|
color: buyDto.color,
|
||||||
|
version: buyDto.version,
|
||||||
|
cards: buyDto.cards,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the Buy Pack button
|
||||||
|
Widget _buildBuyPackButton(Color packColor) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: _isNavigatingToPurchase ? null : _navigateToPurchase,
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: packColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
minimumSize: const Size.fromHeight(56),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _isNavigatingToPurchase
|
||||||
|
? const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.shopping_cart, size: 20),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Buy Pack',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Navigates to the purchase page
|
||||||
|
Future<void> _navigateToPurchase() async {
|
||||||
|
if (_isNavigatingToPurchase) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isNavigatingToPurchase = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
if (mounted) {
|
||||||
|
await context.push('/purchase/${widget.packId}');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isNavigatingToPurchase = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildPackDetails() {
|
Widget _buildPackDetails() {
|
||||||
final pack = _packResponse! as CardPackDto;
|
// Handle both CardPackDto and CardPackBuyDto
|
||||||
|
final pack = _packResponse;
|
||||||
|
if (pack == null) {
|
||||||
|
return const Center(child: Text('Pack not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if pack requires purchase
|
||||||
|
final requiresPurchase = _requiresPurchase();
|
||||||
|
|
||||||
|
// Get pack data - CardPackDto or CardPackBuyDto both have similar structure
|
||||||
|
CardPackDto? packDto;
|
||||||
|
CardPackBuyDto? buyDto;
|
||||||
|
if (pack is CardPackDto) {
|
||||||
|
packDto = pack;
|
||||||
|
} else if (pack is CardPackBuyDto) {
|
||||||
|
buyDto = pack;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use packDto if available, otherwise use buyDto
|
||||||
|
final displayPack = packDto ?? _createDisplayPackFromBuyDto(buyDto!);
|
||||||
final cards = _getDisplayCards();
|
final cards = _getDisplayCards();
|
||||||
final packColor = pack.color?.asColor ?? AppColors.borderGray;
|
final packColor = displayPack.color?.asColor ?? AppColors.borderGray;
|
||||||
|
|
||||||
// Определяем, нужно ли показывать боковую панель
|
// Определяем, нужно ли показывать боковую панель
|
||||||
final showSidebar =
|
final showSidebar =
|
||||||
|
|
@ -273,34 +365,41 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
||||||
children: [
|
children: [
|
||||||
// Кастомный заголовок
|
// Кастомный заголовок
|
||||||
PackDetailsHeader(
|
PackDetailsHeader(
|
||||||
pack: pack,
|
pack: displayPack,
|
||||||
progress: _packProgress,
|
progress: _packProgress,
|
||||||
totalCards: pack.cards.length,
|
totalCards: displayPack.cards.length,
|
||||||
backButtonText: 'к темам',
|
backButtonText: 'к темам',
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Панель управления
|
// Buy Pack button if pack requires purchase
|
||||||
PackDetailsControls(
|
if (requiresPurchase) ...[
|
||||||
isGridView: _isGridView,
|
_buildBuyPackButton(packColor),
|
||||||
onToggleView: () {
|
const SizedBox(height: 16),
|
||||||
setState(() {
|
],
|
||||||
_isGridView = !_isGridView;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onShuffle: () => _shuffleCards(),
|
|
||||||
onToggleFavorites: () {
|
|
||||||
setState(() {
|
|
||||||
_isFavoritesMode = !_isFavoritesMode;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
isFavoritesMode: _isFavoritesMode,
|
|
||||||
isShuffleActive: _isShuffled,
|
|
||||||
shuffleTurns: _shuffleAnimationTurns,
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
// Панель управления (only show if pack is purchased)
|
||||||
|
if (!requiresPurchase) ...[
|
||||||
|
PackDetailsControls(
|
||||||
|
isGridView: _isGridView,
|
||||||
|
onToggleView: () {
|
||||||
|
setState(() {
|
||||||
|
_isGridView = !_isGridView;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onShuffle: () => _shuffleCards(),
|
||||||
|
onToggleFavorites: () {
|
||||||
|
setState(() {
|
||||||
|
_isFavoritesMode = !_isFavoritesMode;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isFavoritesMode: _isFavoritesMode,
|
||||||
|
isShuffleActive: _isShuffled,
|
||||||
|
shuffleTurns: _shuffleAnimationTurns,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
|
||||||
// Основное содержимое
|
// Основное содержимое
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -854,7 +953,16 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
||||||
|
|
||||||
/// Shuffles the cards
|
/// Shuffles the cards
|
||||||
void _shuffleCards() {
|
void _shuffleCards() {
|
||||||
if (_packResponse == null || _packResponse is! CardPackDto) return;
|
if (_packResponse == null) return;
|
||||||
|
|
||||||
|
List<GameCardDto> cardsToShuffle;
|
||||||
|
if (_packResponse is CardPackDto) {
|
||||||
|
cardsToShuffle = (_packResponse as CardPackDto).cards;
|
||||||
|
} else if (_packResponse is CardPackBuyDto) {
|
||||||
|
cardsToShuffle = (_packResponse as CardPackBuyDto).cards;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_shuffleAnimationKey++;
|
_shuffleAnimationKey++;
|
||||||
|
|
@ -862,7 +970,7 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
||||||
_isShuffled = !_isShuffled;
|
_isShuffled = !_isShuffled;
|
||||||
if (_isShuffled) {
|
if (_isShuffled) {
|
||||||
// Create a shuffled copy of the cards
|
// Create a shuffled copy of the cards
|
||||||
_shuffledCards = List<GameCardDto>.from((_packResponse as CardPackDto).cards)..shuffle();
|
_shuffledCards = List<GameCardDto>.from(cardsToShuffle)..shuffle();
|
||||||
log('Cards shuffled', name: 'PackDetailsPage');
|
log('Cards shuffled', name: 'PackDetailsPage');
|
||||||
} else {
|
} else {
|
||||||
// Reset to original order
|
// Reset to original order
|
||||||
|
|
@ -874,13 +982,27 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
||||||
|
|
||||||
/// Gets the cards to display (shuffled or original)
|
/// Gets the cards to display (shuffled or original)
|
||||||
List<GameCardDto> _getDisplayCards() {
|
List<GameCardDto> _getDisplayCards() {
|
||||||
if (_packResponse == null || _packResponse is! CardPackDto) return [];
|
if (_packResponse == null) return [];
|
||||||
|
|
||||||
if (_isShuffled && _shuffledCards.isNotEmpty) {
|
// Handle CardPackDto
|
||||||
return _shuffledCards;
|
if (_packResponse is CardPackDto) {
|
||||||
|
final packDto = _packResponse as CardPackDto;
|
||||||
|
if (_isShuffled && _shuffledCards.isNotEmpty) {
|
||||||
|
return _shuffledCards;
|
||||||
|
}
|
||||||
|
return packDto.cards;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (_packResponse as CardPackDto).cards;
|
// Handle CardPackBuyDto
|
||||||
|
if (_packResponse is CardPackBuyDto) {
|
||||||
|
final buyDto = _packResponse as CardPackBuyDto;
|
||||||
|
if (_isShuffled && _shuffledCards.isNotEmpty) {
|
||||||
|
return _shuffledCards;
|
||||||
|
}
|
||||||
|
return buyDto.cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Проверяет, является ли карточка избранной
|
/// Проверяет, является ли карточка избранной
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,325 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
|
import 'package:mnemo_cards_web_v2/di/app_scope/app_scope_container.dart';
|
||||||
|
import 'package:mnemo_cards_web_v2/domain/services/http_repository_v2.dart';
|
||||||
|
import 'package:mnemo_cards_web_v2/presentation/pages/pack_details/pack_details_page.dart';
|
||||||
|
import 'package:yx_scope_flutter/yx_scope_flutter.dart';
|
||||||
|
|
||||||
|
// Mock classes
|
||||||
|
class MockAppScopeContainer extends Mock implements AppScopeContainer {}
|
||||||
|
|
||||||
|
class MockHttpRepositoryV2 extends Mock implements HttpRepositoryV2 {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late MockAppScopeContainer mockAppScope;
|
||||||
|
late MockHttpRepositoryV2 mockHttpRepository;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockAppScope = MockAppScopeContainer();
|
||||||
|
mockHttpRepository = MockHttpRepositoryV2();
|
||||||
|
when(() => mockAppScope.httpRepository).thenReturn(mockHttpRepository);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('PackDetailsPage - Buy Pack Button', () {
|
||||||
|
testWidgets(
|
||||||
|
'should display Buy Pack button when pack requires purchase',
|
||||||
|
(tester) async {
|
||||||
|
const packId = 'test-pack-1';
|
||||||
|
final buyDto = CardPackBuyDto(
|
||||||
|
id: packId,
|
||||||
|
title: 'Test Pack',
|
||||||
|
subtitle: 'Test Subtitle',
|
||||||
|
cards: [
|
||||||
|
GameCardDto(
|
||||||
|
id: 1,
|
||||||
|
original: 'test',
|
||||||
|
translation: 'тест',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
price: '99₽',
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockHttpRepository.getPack(packId))
|
||||||
|
.thenAnswer((_) async => buyDto);
|
||||||
|
|
||||||
|
final router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/pack/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return PackDetailsPage(packId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/purchase/:packId',
|
||||||
|
builder: (context, state) {
|
||||||
|
final packId = state.pathParameters['packId']!;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text('Purchase $packId')),
|
||||||
|
body: const Center(child: Text('Purchase Page')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ScopeProvider<AppScopeContainer>(
|
||||||
|
scope: mockAppScope,
|
||||||
|
child: MaterialApp.router(
|
||||||
|
routerConfig: router,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for pack to load
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Verify Buy Pack button is displayed
|
||||||
|
expect(find.text('Buy Pack'), findsOneWidget);
|
||||||
|
expect(find.byIcon(Icons.shopping_cart), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'should navigate to PurchasePage with correct packId when Buy Pack button is tapped',
|
||||||
|
(tester) async {
|
||||||
|
const packId = 'test-pack-2';
|
||||||
|
final buyDto = CardPackBuyDto(
|
||||||
|
id: packId,
|
||||||
|
title: 'Test Pack',
|
||||||
|
subtitle: 'Test Subtitle',
|
||||||
|
cards: [
|
||||||
|
GameCardDto(
|
||||||
|
id: 1,
|
||||||
|
original: 'test',
|
||||||
|
translation: 'тест',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
price: '99₽',
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockHttpRepository.getPack(packId))
|
||||||
|
.thenAnswer((_) async => buyDto);
|
||||||
|
|
||||||
|
final router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/pack/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return PackDetailsPage(packId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/purchase/:packId',
|
||||||
|
builder: (context, state) {
|
||||||
|
final packId = state.pathParameters['packId']!;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text('Purchase $packId')),
|
||||||
|
body: Center(child: Text('Purchase Page for $packId')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ScopeProvider<AppScopeContainer>(
|
||||||
|
scope: mockAppScope,
|
||||||
|
child: MaterialApp.router(
|
||||||
|
routerConfig: router,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for pack to load
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Tap Buy Pack button
|
||||||
|
await tester.tap(find.text('Buy Pack'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Verify navigation to PurchasePage
|
||||||
|
expect(find.text('Purchase Page for $packId'), findsOneWidget);
|
||||||
|
expect(find.text('Purchase $packId'), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'should show loading indicator when navigating to purchase page',
|
||||||
|
(tester) async {
|
||||||
|
const packId = 'test-pack-3';
|
||||||
|
final buyDto = CardPackBuyDto(
|
||||||
|
id: packId,
|
||||||
|
title: 'Test Pack',
|
||||||
|
subtitle: 'Test Subtitle',
|
||||||
|
cards: [
|
||||||
|
GameCardDto(
|
||||||
|
id: 1,
|
||||||
|
original: 'test',
|
||||||
|
translation: 'тест',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
price: '99₽',
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockHttpRepository.getPack(packId))
|
||||||
|
.thenAnswer((_) async => buyDto);
|
||||||
|
|
||||||
|
final router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/pack/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return PackDetailsPage(packId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/purchase/:packId',
|
||||||
|
builder: (context, state) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: Text('Purchase Page')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ScopeProvider<AppScopeContainer>(
|
||||||
|
scope: mockAppScope,
|
||||||
|
child: MaterialApp.router(
|
||||||
|
routerConfig: router,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for pack to load
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Tap Buy Pack button
|
||||||
|
await tester.tap(find.text('Buy Pack'));
|
||||||
|
|
||||||
|
// Pump once to trigger navigation state
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify loading indicator appears (CircularProgressIndicator)
|
||||||
|
expect(find.byType(CircularProgressIndicator), findsWidgets);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'should not display Buy Pack button when pack is already purchased',
|
||||||
|
(tester) async {
|
||||||
|
const packId = 'test-pack-4';
|
||||||
|
final packDto = CardPackDto(
|
||||||
|
id: packId,
|
||||||
|
title: 'Test Pack',
|
||||||
|
subtitle: 'Test Subtitle',
|
||||||
|
cards: [
|
||||||
|
GameCardDto(
|
||||||
|
id: 1,
|
||||||
|
original: 'test',
|
||||||
|
translation: 'тест',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockHttpRepository.getPack(packId))
|
||||||
|
.thenAnswer((_) async => packDto);
|
||||||
|
|
||||||
|
final router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/pack/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return PackDetailsPage(packId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ScopeProvider<AppScopeContainer>(
|
||||||
|
scope: mockAppScope,
|
||||||
|
child: MaterialApp.router(
|
||||||
|
routerConfig: router,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for pack to load
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Verify Buy Pack button is NOT displayed
|
||||||
|
expect(find.text('Buy Pack'), findsNothing);
|
||||||
|
expect(find.byIcon(Icons.shopping_cart), findsNothing);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'should style Buy Pack button with pack color',
|
||||||
|
(tester) async {
|
||||||
|
const packId = 'test-pack-5';
|
||||||
|
final buyDto = CardPackBuyDto(
|
||||||
|
id: packId,
|
||||||
|
title: 'Test Pack',
|
||||||
|
subtitle: 'Test Subtitle',
|
||||||
|
color: '#FF5733', // Red color
|
||||||
|
cards: [
|
||||||
|
GameCardDto(
|
||||||
|
id: 1,
|
||||||
|
original: 'test',
|
||||||
|
translation: 'тест',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
price: '99₽',
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockHttpRepository.getPack(packId))
|
||||||
|
.thenAnswer((_) async => buyDto);
|
||||||
|
|
||||||
|
final router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/pack/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return PackDetailsPage(packId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ScopeProvider<AppScopeContainer>(
|
||||||
|
scope: mockAppScope,
|
||||||
|
child: MaterialApp.router(
|
||||||
|
routerConfig: router,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for pack to load
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Find the FilledButton
|
||||||
|
final button = tester.widget<FilledButton>(
|
||||||
|
find.byType(FilledButton),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify button styling
|
||||||
|
expect(button.style, isNotNull);
|
||||||
|
expect(button.style?.backgroundColor, isNotNull);
|
||||||
|
expect(button.style?.minimumSize, const Size.fromHeight(56));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue