Filters: three-way hidden files and external storage
Hidden: hide / only hidden / all + hidden. External: cloud only / only external / all + external; with 'all' Files shows the external items in a collapsible section. Old show-hidden bool prefs are still read as fallback. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
944b87b054
commit
3c60f8c709
@@ -0,0 +1,129 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:noo/models/nextcloud_item.dart';
|
||||
import 'package:noo/providers/connectivity_controller.dart';
|
||||
import 'package:noo/providers/files_controller.dart';
|
||||
import 'package:noo/providers/session_controller.dart';
|
||||
|
||||
NextcloudItem _item(String path, {String? mountType}) => NextcloudItem(
|
||||
id: path,
|
||||
name: path.split('/').last,
|
||||
path: path,
|
||||
type: NextcloudItemType.file,
|
||||
size: 1,
|
||||
lastModified: DateTime(2026),
|
||||
mountType: mountType,
|
||||
);
|
||||
|
||||
/// The shared hidden / storage-scope filtering (`applyCommonFilters`) and
|
||||
/// how a pref saved before the three-way hidden filter is read back.
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUpAll(() {
|
||||
final messenger =
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
messenger.setMockMethodCallHandler(
|
||||
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
||||
(call) async => call.method == 'readAll' ? <String, String>{} : null,
|
||||
);
|
||||
messenger.setMockMethodCallHandler(
|
||||
const MethodChannel('dev.fluttercommunity.plus/connectivity'),
|
||||
(call) async => call.method == 'check' ? <String>['none'] : null,
|
||||
);
|
||||
messenger.setMockStreamHandler(
|
||||
const EventChannel('dev.fluttercommunity.plus/connectivity_status'),
|
||||
MockStreamHandler.inline(onListen: (arguments, events) {}),
|
||||
);
|
||||
});
|
||||
|
||||
setUp(() => SharedPreferences.setMockInitialValues({}));
|
||||
|
||||
final items = [
|
||||
_item('/a.txt'),
|
||||
_item('/.secret'),
|
||||
_item('/docs/.git/config'),
|
||||
_item('/nas', mountType: 'external'),
|
||||
_item('/.nas-hidden', mountType: 'external'),
|
||||
];
|
||||
List<String> paths(List<NextcloudItem> l) => l.map((i) => i.path).toList();
|
||||
|
||||
FilesController build() =>
|
||||
FilesController(SessionController(ConnectivityController()));
|
||||
|
||||
test('hidden filter: hide / only / include', () {
|
||||
final files = build()..setStorageScope(StorageScope.all);
|
||||
List<String> run(HiddenFilesFilter h) => paths(
|
||||
files.applyCommonFilters(items, showFavoritesOnly: false, hidden: h),
|
||||
);
|
||||
expect(run(HiddenFilesFilter.hide), ['/a.txt', '/nas']);
|
||||
expect(run(HiddenFilesFilter.only), [
|
||||
'/.secret',
|
||||
'/docs/.git/config',
|
||||
'/.nas-hidden',
|
||||
]);
|
||||
expect(run(HiddenFilesFilter.include), paths(items));
|
||||
});
|
||||
|
||||
test('storage scope: cloud / external / all', () {
|
||||
final files = build();
|
||||
List<String> run(StorageScope s) {
|
||||
files.setStorageScope(s);
|
||||
return paths(
|
||||
files.applyCommonFilters(
|
||||
items,
|
||||
showFavoritesOnly: false,
|
||||
hidden: HiddenFilesFilter.include,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
expect(run(StorageScope.cloud), [
|
||||
'/a.txt',
|
||||
'/.secret',
|
||||
'/docs/.git/config',
|
||||
]);
|
||||
expect(run(StorageScope.external), ['/nas', '/.nas-hidden']);
|
||||
expect(run(StorageScope.all), paths(items));
|
||||
});
|
||||
|
||||
test('hidden + external combine', () {
|
||||
final files = build()..setStorageScope(StorageScope.external);
|
||||
expect(
|
||||
paths(
|
||||
files.applyCommonFilters(
|
||||
items,
|
||||
showFavoritesOnly: false,
|
||||
hidden: HiddenFilesFilter.only,
|
||||
),
|
||||
),
|
||||
['/.nas-hidden'],
|
||||
);
|
||||
});
|
||||
|
||||
group('loadHiddenFilter', () {
|
||||
Future<HiddenFilesFilter> load(Map<String, Object> values) async {
|
||||
SharedPreferences.setMockInitialValues(values);
|
||||
return FilesController.loadHiddenFilter(
|
||||
await SharedPreferences.getInstance(),
|
||||
'new',
|
||||
'old',
|
||||
);
|
||||
}
|
||||
|
||||
test('defaults to hide', () async {
|
||||
expect(await load({}), HiddenFilesFilter.hide);
|
||||
});
|
||||
|
||||
test('the old show-hidden bool maps to hide / include', () async {
|
||||
expect(await load({'old': true}), HiddenFilesFilter.include);
|
||||
expect(await load({'old': false}), HiddenFilesFilter.hide);
|
||||
});
|
||||
|
||||
test('the new key wins and an unknown value falls back to hide', () async {
|
||||
expect(await load({'new': 'only', 'old': true}), HiddenFilesFilter.only);
|
||||
expect(await load({'new': 'bogus'}), HiddenFilesFilter.hide);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:noo/providers/files_controller.dart';
|
||||
import 'package:noo/widgets/filter_mode_rows.dart';
|
||||
|
||||
import 'noo/noo_test_utils.dart';
|
||||
|
||||
void main() {
|
||||
setUpNooTests();
|
||||
|
||||
group('HiddenFilesFilterRow', () {
|
||||
testNooWidgets(
|
||||
'only the selected option shows its label, tapping another reports it',
|
||||
(tester, theme, c) async {
|
||||
var value = HiddenFilesFilter.hide;
|
||||
await pumpNoo(
|
||||
tester,
|
||||
StatefulBuilder(
|
||||
builder: (context, setState) => SizedBox(
|
||||
width: 360,
|
||||
child: HiddenFilesFilterRow(
|
||||
value: value,
|
||||
onChanged: (v) => setState(() => value = v),
|
||||
),
|
||||
),
|
||||
),
|
||||
theme: theme,
|
||||
);
|
||||
expect(find.text('Hidden files'), findsOneWidget);
|
||||
expect(find.text('Hide'), findsOneWidget);
|
||||
expect(find.text('Only hidden'), findsNothing);
|
||||
|
||||
await tester.tap(find.byIcon(LucideIcons.layers));
|
||||
await tester.pumpAndSettle();
|
||||
expect(value, HiddenFilesFilter.include);
|
||||
expect(find.text('All + hidden'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('StorageScopeRow', () {
|
||||
testNooWidgets('offers cloud only / only external / all + external', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
var value = StorageScope.cloud;
|
||||
await pumpNoo(
|
||||
tester,
|
||||
StatefulBuilder(
|
||||
builder: (context, setState) => SizedBox(
|
||||
width: 360,
|
||||
child: StorageScopeRow(
|
||||
value: value,
|
||||
onChanged: (v) => setState(() => value = v),
|
||||
),
|
||||
),
|
||||
),
|
||||
theme: theme,
|
||||
);
|
||||
expect(find.text('External storage'), findsOneWidget);
|
||||
expect(find.text('Cloud only'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byIcon(LucideIcons.hardDrive).last);
|
||||
await tester.pumpAndSettle();
|
||||
expect(value, StorageScope.external);
|
||||
expect(find.text('Only external'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byIcon(LucideIcons.layers));
|
||||
await tester.pumpAndSettle();
|
||||
expect(value, StorageScope.all);
|
||||
expect(find.text('All + external'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user