Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/models/nutrition/ingredient.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class Ingredient {
@JsonKey(required: true)
final String name;

/// Brand of the product
@JsonKey(name: 'brand')
final String? brand;

@JsonKey(required: true, name: 'created')
final DateTime created;

Expand Down Expand Up @@ -123,6 +127,7 @@ class Ingredient {
required this.id,
required this.code,
required this.name,
this.brand,
required this.created,
required this.energy,
required this.carbohydrates,
Expand Down
2 changes: 2 additions & 0 deletions lib/models/nutrition/ingredient.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion lib/widgets/nutrition/ingredient_dialogs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,22 @@ class IngredientDetails extends StatelessWidget {
}

return AlertDialog(
title: (snapshot.hasData) ? Text(ingredient!.name) : null,
title: snapshot.hasData
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(ingredient!.name),
if (ingredient.brand != null && ingredient.brand!.isNotEmpty)
Text(
ingredient.brand!,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: DefaultTextStyle.of(context).style.color?.withValues(alpha: 0.6),
),
),
],
)
: null,
content: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
Expand Down
15 changes: 13 additions & 2 deletions lib/widgets/nutrition/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,19 @@ class _IngredientTypeaheadState extends ConsumerState<IngredientTypeahead> {
: const CircleIconAvatar(
Icon(Icons.image, color: Colors.grey),
),
title: Text(
ingredient.name,
title: Text.rich(
TextSpan(
children: [
TextSpan(text: ingredient.name),
if (ingredient.brand != null && ingredient.brand!.isNotEmpty)
TextSpan(
text: ' ${ingredient.brand}',
style: TextStyle(
color: DefaultTextStyle.of(context).style.color?.withValues(alpha: 0.6),
),
),
],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
Expand Down
21 changes: 21 additions & 0 deletions test/nutrition/ingredient_typeahead_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,27 @@ void main() {
expect(find.text('Vegan'), findsNothing);
});

testWidgets('Shows brand inline in search result tile', (WidgetTester tester) async {
// ingredient3 (Broccoli cake) has brand 'Weightwatchers'
when(
mockNutrition.searchIngredient(
any,
languageCode: anyNamed('languageCode'),
searchLanguage: anyNamed('searchLanguage'),
isVegan: anyNamed('isVegan'),
isVegetarian: anyNamed('isVegetarian'),
nutriscoreMax: anyNamed('nutriscoreMax'),
),
).thenAnswer((_) => Future.value([ingredient3]));

await tester.pumpWidget(createWidgetUnderTest());
await tester.enterText(find.byType(TextFormField), 'Broccoli');
await tester.pump(const Duration(milliseconds: 600));
await tester.pumpAndSettle();

expect(find.textContaining('Weightwatchers'), findsOneWidget);
});

testWidgets('Shows no dietary chips when ingredient has no info', (WidgetTester tester) async {
// ingredient2 (Burger soup) has no dietary info
when(
Expand Down
61 changes: 61 additions & 0 deletions test/widgets/nutrition/ingredient_dialogs_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,34 @@ import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/nutrition/ingredient.dart';
import 'package:wger/widgets/nutrition/ingredient_dialogs.dart';

import '../../../test_data/nutritional_plans.dart';

Future<void> pumpIngredientDetailsDialog(
WidgetTester tester, {
required AsyncSnapshot<Ingredient> snapshot,
}) async {
await tester.pumpWidget(
MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: Builder(
builder: (context) => ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (_) => IngredientDetails(snapshot),
);
},
child: const Text('Show Dialog'),
),
),
),
),
);
}

Future<void> pumpIngredientScanDialog(
WidgetTester tester, {
required AsyncSnapshot<Ingredient?> snapshot,
Expand Down Expand Up @@ -54,6 +82,39 @@ Future<void> pumpIngredientScanDialog(
}

void main() {
group('IngredientDetails tests', () {
testWidgets('shows brand below name in title when ingredient has a brand', (
WidgetTester tester,
) async {
// ingredient3 (Broccoli cake, brand: 'Weightwatchers')
final snapshot = AsyncSnapshot<Ingredient>.withData(ConnectionState.done, ingredient3);

await pumpIngredientDetailsDialog(tester, snapshot: snapshot);
await tester.tap(find.text('Show Dialog'));
await tester.pumpAndSettle();

expect(find.byType(AlertDialog), findsOneWidget);
expect(find.text('Broccoli cake'), findsOneWidget);
expect(find.text('Weightwatchers'), findsOneWidget);
});

testWidgets('does not show brand in title when ingredient has no brand', (
WidgetTester tester,
) async {
// ingredient1 (Water, brand: null)
final snapshot = AsyncSnapshot<Ingredient>.withData(ConnectionState.done, ingredient1);

await pumpIngredientDetailsDialog(tester, snapshot: snapshot);
await tester.tap(find.text('Show Dialog'));
await tester.pumpAndSettle();

expect(find.byType(AlertDialog), findsOneWidget);
expect(find.text('Water'), findsOneWidget);
// brand: null must not render as the literal string "null"
expect(find.text('null'), findsNothing);
});
});

group('IngredientScanResultDialog tests', () {
const testBarcode = '1234567890123';

Expand Down
5 changes: 5 additions & 0 deletions test_data/nutritional_plans.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ final ingredient1 = Ingredient(
id: 1,
code: '123456787',
name: 'Water',
brand: null,
created: DateTime(2021, 5, 1),
energy: 500,
carbohydrates: 10,
Expand All @@ -52,6 +53,7 @@ final ingredient2 = Ingredient(
id: 2,
code: '123456788',
name: 'Burger soup',
brand: null,
created: DateTime(2021, 5, 10),
energy: 25,
carbohydrates: 10,
Expand All @@ -69,6 +71,7 @@ final ingredient3 = Ingredient(
id: 3,
code: '123456789',
name: 'Broccoli cake',
brand: 'Weightwatchers',
created: DateTime(2021, 5, 2),
energy: 1200,
carbohydrates: 110,
Expand All @@ -86,6 +89,7 @@ final muesli = Ingredient(
id: 1,
code: '123456787',
name: 'Müsli',
brand: 'Spar Gourmet',
created: DateTime(2021, 5, 1),
energy: 500,
carbohydrates: 10,
Expand All @@ -106,6 +110,7 @@ final milk = Ingredient(
id: 1,
code: '123456787',
name: 'Milk',
brand: null,
created: DateTime(2021, 5, 1),
energy: 500,
carbohydrates: 10,
Expand Down