Skip to content

Commit

Permalink
test(neon_framework): test public dialogs
Browse files Browse the repository at this point in the history
Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
  • Loading branch information
Leptopoda committed Dec 28, 2023
1 parent 1adb575 commit 8770601
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 7 deletions.
11 changes: 6 additions & 5 deletions packages/app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1223,12 +1223,13 @@ packages:
source: hosted
version: "0.9.2"
tray_manager:
dependency: transitive
dependency: "direct overridden"
description:
name: tray_manager
sha256: b1975a05e0c6999e983cf9a58a6a098318c896040ccebac5398a3cc9e43b9c69
url: "https://pub.dev"
source: hosted
path: "."
ref: "fix/libayatana-set-icon-deprecation"
resolved-ref: "056da3fef20e75877e8d2a4ae73d93f51a0f77cc"
url: "https://github.com/provokateurin/tray_manager.git"
source: git
version: "0.2.0"
typed_data:
dependency: transitive
Expand Down
6 changes: 6 additions & 0 deletions packages/app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ dev_dependencies:
shared_preferences: any
vector_graphics_compiler: any

dependency_overrides:
tray_manager:
git:
url: https://github.com/provokateurin/tray_manager.git
ref: fix/libayatana-set-icon-deprecation

flutter:
uses-material-design: true
assets:
Expand Down
6 changes: 5 additions & 1 deletion packages/app/pubspec_overrides.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# melos_managed_dependency_overrides: dynamite_runtime,neon_framework,neon_lints,nextcloud,sort_box
# melos_managed_dependency_overrides: dynamite_runtime,neon_framework,neon_lints,nextcloud,sort_box,tray_manager
dependency_overrides:
dynamite_runtime:
path: ../dynamite/dynamite_runtime
Expand All @@ -22,3 +22,7 @@ dependency_overrides:
path: ../nextcloud
sort_box:
path: ../sort_box
tray_manager:
git:
url: https://github.com/provokateurin/tray_manager.git
ref: fix/libayatana-set-icon-deprecation
24 changes: 23 additions & 1 deletion packages/neon_framework/lib/src/theme/theme.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:neon_framework/src/theme/branding.dart';
import 'package:neon_framework/src/theme/colors.dart';
import 'package:neon_framework/src/theme/neon.dart';
import 'package:neon_framework/src/theme/server.dart';
Expand All @@ -19,7 +20,23 @@ class AppTheme {
this.useNextcloudTheme = false,
this.oledAsDark = false,
this.appThemes,
});
}) : platform = null;

@visibleForTesting
const AppTheme.test({
this.serverTheme = const ServerTheme(nextcloudTheme: null),
this.deviceThemeLight,
this.deviceThemeDark,
this.neonTheme = const NeonTheme(
branding: Branding(
name: 'Test App',
logo: Placeholder(),
),
),
this.useNextcloudTheme = false,
this.oledAsDark = false,
this.platform,
}) : appThemes = null;

/// The theme provided by the Nextcloud server.
final ServerTheme serverTheme;
Expand All @@ -42,6 +59,10 @@ class AppTheme {
/// The base theme for the Neon app.
final NeonTheme neonTheme;

/// The platform the material widgets should adapt to target.
@visibleForTesting
final TargetPlatform? platform;

ColorScheme _buildColorScheme(final Brightness brightness) {
ColorScheme? colorScheme;

Expand Down Expand Up @@ -83,6 +104,7 @@ class AppTheme {

return ThemeData(
useMaterial3: true,
platform: platform,
colorScheme: colorScheme,
scaffoldBackgroundColor: colorScheme.background,
cardColor: colorScheme.background,
Expand Down
2 changes: 2 additions & 0 deletions packages/neon_framework/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ dependencies:

dev_dependencies:
build_runner: ^2.4.7
flutter_test:
sdk: flutter
go_router_builder: ^2.4.0
json_serializable: ^6.7.1
mocktail: ^1.0.2
Expand Down
196 changes: 196 additions & 0 deletions packages/neon_framework/test/dialog_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:neon_framework/l10n/localizations_en.dart';
import 'package:neon_framework/src/theme/theme.dart';
import 'package:neon_framework/src/widgets/dialog.dart';
import 'package:neon_framework/utils.dart';

Widget wrapDialog(final Widget dialog, [final TargetPlatform platform = TargetPlatform.android]) {
final theme = AppTheme.test(platform: platform);
const locale = Locale('en');

return MaterialApp(
theme: theme.lightTheme,
localizationsDelegates: NeonLocalizations.localizationsDelegates,
supportedLocales: NeonLocalizations.supportedLocales,
locale: locale,
home: dialog,
);
}

void main() {
group('dialog', () {
group('NeonConfirmationDialog', () {
testWidgets('NeonConfirmationDialog widget', (final widgetTester) async {
const title = 'My Title';
var dialog = const NeonConfirmationDialog(title: title);
await widgetTester.pumpWidget(wrapDialog(dialog));

expect(find.text(title), findsOneWidget);
expect(find.byType(NeonDialogAction), findsExactly(2));

// Not true on cupertino platforms
expect(find.byType(OutlinedButton), findsOneWidget);
expect(find.byType(ElevatedButton), findsOneWidget);

dialog = const NeonConfirmationDialog(
title: title,
isDestructive: false,
);

await widgetTester.pumpWidget(wrapDialog(dialog));

expect(find.byType(NeonDialogAction), findsExactly(2));
expect(find.byType(OutlinedButton), findsExactly(2));

const icon = Icon(Icons.error);
const content = SizedBox(key: Key('content'));
const confirmAction = SizedBox(key: Key('confirmAction'));
const declineAction = SizedBox(key: Key('declineAction'));
dialog = const NeonConfirmationDialog(
title: title,
icon: icon,
content: content,
confirmAction: confirmAction,
declineAction: declineAction,
);
await widgetTester.pumpWidget(wrapDialog(dialog));

expect(find.byIcon(Icons.error), findsOneWidget);
expect(find.byKey(const Key('content')), findsOneWidget);
expect(find.byKey(const Key('confirmAction')), findsOneWidget);
expect(find.byKey(const Key('declineAction')), findsOneWidget);
});

testWidgets('NeonConfirmationDialog actions', (final widgetTester) async {
const title = 'My Title';
await widgetTester.pumpWidget(wrapDialog(const Placeholder()));
final context = widgetTester.element(find.byType(Placeholder));

// confirm
var result = showConfirmationDialog(context: context, title: title);
await widgetTester.pumpAndSettle();
await widgetTester.tap(find.text(NeonLocalizationsEn().actionContinue));
expect(await result, isTrue);

// decline
result = showConfirmationDialog(context: context, title: title);
await widgetTester.pumpAndSettle();
await widgetTester.tap(find.text(NeonLocalizationsEn().actionCancel));
expect(await result, isFalse);

// cancel by tapping outside
result = showConfirmationDialog(context: context, title: title);
await widgetTester.pumpAndSettle();
await widgetTester.tapAt(Offset.zero);
expect(await result, isFalse);
});
});

group('NeonRenameDialog', () {
testWidgets('NeonRenameDialog widget', (final widgetTester) async {
const title = 'My Title';
const value = 'My value';
const dialog = NeonRenameDialog(title: title, value: value);
await widgetTester.pumpWidget(wrapDialog(dialog));

expect(find.text(title), findsExactly(2), reason: 'The title is also used for the confirmation button');
expect(find.text(value), findsOneWidget);
expect(find.byType(TextFormField), findsOneWidget);
});

testWidgets('NeonRenameDialog actions', (final widgetTester) async {
const title = 'My Title';
const value = 'My value';
await widgetTester.pumpWidget(wrapDialog(const Placeholder()));
final context = widgetTester.element(find.byType(Placeholder));

// Equal value should not submit
var result = showRenameDialog(context: context, title: title, initialValue: value);
await widgetTester.pumpAndSettle();
await widgetTester.enterText(find.byType(TextFormField), value);
await widgetTester.tap(find.byType(NeonDialogAction));
expect(await result, isNull);

// Empty value should not submit
result = showRenameDialog(context: context, title: title, initialValue: value);
await widgetTester.pumpAndSettle();
await widgetTester.enterText(find.byType(TextFormField), '');
await widgetTester.tap(find.byType(NeonDialogAction));

// Different value should submit
await widgetTester.enterText(find.byType(TextFormField), 'My new value');
await widgetTester.tap(find.byType(NeonDialogAction));
expect(await result, equals('My new value'));

// Submit via keyboard
result = showRenameDialog(context: context, title: title, initialValue: value);
await widgetTester.pumpAndSettle();
await widgetTester.enterText(find.byType(TextFormField), 'My new value');
await widgetTester.testTextInput.receiveAction(TextInputAction.done);
await widgetTester.tap(find.byType(NeonDialogAction));
expect(await result, equals('My new value'));
});
});

group('NeonErrorDialog', () {
testWidgets('NeonErrorDialog widget', (final widgetTester) async {
const title = 'My Title';
const content = 'My content';
var dialog = const NeonErrorDialog(content: content, title: title);
await widgetTester.pumpWidget(wrapDialog(dialog));

expect(find.byIcon(Icons.error), findsOneWidget);
expect(find.text(title), findsOneWidget);
expect(find.text(content), findsOneWidget);
expect(find.byType(NeonDialogAction), findsOneWidget);

dialog = const NeonErrorDialog(content: content);
await widgetTester.pumpWidget(wrapDialog(dialog));

expect(find.text(NeonLocalizationsEn().errorDialog), findsOneWidget);
});

testWidgets('NeonErrorDialog actions', (final widgetTester) async {
const content = 'My content';
await widgetTester.pumpWidget(wrapDialog(const Placeholder()));
final context = widgetTester.element(find.byType(Placeholder));

final result = showErrorDialog(context: context, message: content);
await widgetTester.pumpAndSettle();
await widgetTester.tap(find.text(NeonLocalizationsEn().actionClose));
await result;
});
});

testWidgets('UnimplementedDialog', (final widgetTester) async {
const title = 'My Title';
await widgetTester.pumpWidget(wrapDialog(const Placeholder()));
final context = widgetTester.element(find.byType(Placeholder));

final result = showUnimplementedDialog(context: context, title: title);
await widgetTester.pumpAndSettle();
await widgetTester.tap(find.text(NeonLocalizationsEn().actionClose));
await result;
});

testWidgets('NeonDialog', (final widgetTester) async {
var dialog = const NeonDialog(
actions: [],
);
await widgetTester.pumpWidget(wrapDialog(dialog, TargetPlatform.macOS));
expect(
find.byType(NeonDialogAction),
findsOneWidget,
reason: 'Dialogs can not be dismissed on cupertino platforms. Expecting a fallback action.',
);

dialog = const NeonDialog(
automaticallyShowCancel: false,
actions: [],
);
await widgetTester.pumpWidget(wrapDialog(dialog, TargetPlatform.macOS));
expect(find.byType(NeonDialogAction), findsNothing);
});
});
}

0 comments on commit 8770601

Please sign in to comment.