diff --git a/packages/devtools_app/lib/src/screens/logging/logging_controller.dart b/packages/devtools_app/lib/src/screens/logging/logging_controller.dart index 979ec60cbee..ccf6f1a6e46 100644 --- a/packages/devtools_app/lib/src/screens/logging/logging_controller.dart +++ b/packages/devtools_app/lib/src/screens/logging/logging_controller.dart @@ -118,10 +118,10 @@ class LoggingController extends DisposableController /// The toggle filters available for the Logging screen. @override - List> createToggleFilters() => toggleFilters; + List> createSettingFilters() => settingFilters; @visibleForTesting - static final toggleFilters = >[ + static final settingFilters = >[ if (serviceConnection.serviceManager.connectedApp?.isFlutterAppNow ?? true) ...[ ToggleFilter( @@ -129,20 +129,20 @@ class LoggingController extends DisposableController 'times, image sizes)', includeCallback: (log) => !_verboseFlutterFrameworkLogKinds .any((kind) => kind.caseInsensitiveEquals(log.kind)), - enabledByDefault: true, + defaultValue: true, ), ToggleFilter( name: 'Hide verbose Flutter service logs (service extension state ' 'changes)', includeCallback: (log) => !_verboseFlutterServiceLogKinds .any((kind) => kind.caseInsensitiveEquals(log.kind)), - enabledByDefault: true, + defaultValue: true, ), ], ToggleFilter( name: 'Hide garbage collection logs', includeCallback: (log) => !log.kind.caseInsensitiveEquals(_gcLogKind), - enabledByDefault: true, + defaultValue: true, ), ]; @@ -595,11 +595,10 @@ class LoggingController extends DisposableController super.filterData(filter); bool filterCallback(LogData log) { - final filteredOutByToggleFilters = filter.toggleFilters.any( - (toggleFilter) => - toggleFilter.enabled.value && !toggleFilter.includeCallback(log), + final filteredOutBySettingFilters = filter.settingFilters.any( + (settingFilter) => !settingFilter.includeData(log), ); - if (filteredOutByToggleFilters) return false; + if (filteredOutBySettingFilters) return false; final queryFilter = filter.queryFilter; if (!queryFilter.isEmpty) { diff --git a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_model.dart b/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_model.dart index d3e0a9fc4b1..1182cb72b9c 100644 --- a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_model.dart +++ b/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_model.dart @@ -125,7 +125,7 @@ class LoggingTableModel extends DisposableController /// The toggle filters available for the Logging screen. @override - List> createToggleFilters() => [ + List> createSettingFilters() => [ if (serviceConnection.serviceManager.connectedApp?.isFlutterAppNow ?? true) ...[ ToggleFilter( @@ -133,20 +133,20 @@ class LoggingTableModel extends DisposableController 'times, image sizes)', includeCallback: (log) => !_verboseFlutterFrameworkLogKinds .any((kind) => kind.caseInsensitiveEquals(log.kind)), - enabledByDefault: true, + defaultValue: true, ), ToggleFilter( name: 'Hide verbose Flutter service logs (service extension state ' 'changes)', includeCallback: (log) => !_verboseFlutterServiceLogKinds .any((kind) => kind.caseInsensitiveEquals(log.kind)), - enabledByDefault: true, + defaultValue: true, ), ], ToggleFilter( name: 'Hide garbage collection logs', includeCallback: (log) => !log.kind.caseInsensitiveEquals(_gcLogKind), - enabledByDefault: true, + defaultValue: true, ), ]; @@ -616,11 +616,10 @@ class LoggingTableModel extends DisposableController bool _filterCallback(LogDataV2 log) { final filter = activeFilter.value; - final filteredOutByToggleFilters = filter.toggleFilters.any( - (toggleFilter) => - toggleFilter.enabled.value && !toggleFilter.includeCallback(log), + final filteredOutBySettingFilters = filter.settingFilters.any( + (settingFilter) => !settingFilter.includeData(log), ); - if (filteredOutByToggleFilters) return false; + if (filteredOutBySettingFilters) return false; final queryFilter = filter.queryFilter; if (!queryFilter.isEmpty) { diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profiler_controller.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profiler_controller.dart index 87fd3c9dec4..179bb8a3a76 100644 --- a/packages/devtools_app/lib/src/screens/profiler/cpu_profiler_controller.dart +++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profiler_controller.dart @@ -137,21 +137,23 @@ class CpuProfilerController extends DisposableController /// The toggle filters available for the CPU profiler. @override - List> createToggleFilters() => [ + List> createSettingFilters() => [ ToggleFilter( name: 'Hide Native code', includeCallback: (stackFrame) => !stackFrame.isNative, - enabledByDefault: true, + defaultValue: true, ), ToggleFilter( name: 'Hide core Dart libraries', includeCallback: (stackFrame) => !stackFrame.isDartCore, + defaultValue: false, ), if (serviceConnection.serviceManager.connectedApp?.isFlutterAppNow ?? true) ToggleFilter( name: 'Hide core Flutter libraries', includeCallback: (stackFrame) => !stackFrame.isFlutterCore, + defaultValue: false, ), ]; @@ -675,12 +677,10 @@ class CpuProfilerController extends DisposableController }) { filter ??= activeFilter.value; bool filterCallback(CpuStackFrame stackFrame) { - for (final toggleFilter in filter!.toggleFilters) { - if (toggleFilter.enabled.value && - !toggleFilter.includeCallback(stackFrame)) { - return false; - } - } + final filteredOutBySettingFilters = filter!.settingFilters.any( + (settingFilter) => !settingFilter.includeData(stackFrame), + ); + if (filteredOutBySettingFilters) return false; final queryFilter = filter.queryFilter; if (!queryFilter.isEmpty) { diff --git a/packages/devtools_app/lib/src/shared/ui/filter.dart b/packages/devtools_app/lib/src/shared/ui/filter.dart index 3132704ad5b..799a42302ae 100644 --- a/packages/devtools_app/lib/src/shared/ui/filter.dart +++ b/packages/devtools_app/lib/src/shared/ui/filter.dart @@ -21,7 +21,7 @@ import '../primitives/utils.dart'; /// storing the state of those filters. /// /// To use this mixin, you must implement [filterData] as well as either or both -/// [createToggleFilters] and [createQueryFilterArgs]. +/// [createSettingFilters] and [createQueryFilterArgs]. /// /// Classes mixing in [FilterControllerMixin] must also extend /// [DisposableController] and mixin [AutoDisposeControllerMixin], and a class @@ -35,19 +35,18 @@ mixin FilterControllerMixin on DisposableController final useRegExp = ValueNotifier(false); - // TODO(kenz): replace [Filter] class with a record when available. ValueListenable> get activeFilter => _activeFilter; late final _activeFilter = ValueNotifier>( Filter( queryFilter: QueryFilter.empty(args: _queryFilterArgs), - toggleFilters: _toggleFilters, + settingFilters: _settingFilters, ), ); void setActiveFilter({ String? query, - List>? toggleFilters, + List>? settingFilters, }) { _activeFilter.value = Filter( queryFilter: query != null @@ -57,7 +56,7 @@ mixin FilterControllerMixin on DisposableController useRegExp: useRegExp.value, ) : QueryFilter.empty(args: _queryFilterArgs), - toggleFilters: toggleFilters ?? _toggleFilters, + settingFilters: settingFilters ?? _settingFilters, ); } @@ -67,9 +66,10 @@ mixin FilterControllerMixin on DisposableController }); } - late final List> _toggleFilters = createToggleFilters(); + late final List> _settingFilters = + createSettingFilters(); - List> createToggleFilters() => []; + List> createSettingFilters() => []; late final _queryFilterArgs = createQueryFilterArgs(); @@ -79,10 +79,9 @@ mixin FilterControllerMixin on DisposableController bool get isFilterActive { final filter = activeFilter.value; final queryFilterActive = !filter.queryFilter.isEmpty; - final toggleFilterActive = filter.toggleFilters.any( - (filter) => filter.enabled.value, - ); - return queryFilterActive || toggleFilterActive; + final settingFilterActive = + filter.settingFilters.any((filter) => filter.enabled); + return queryFilterActive || settingFilterActive; } // TODO(kenz): de-dupe the filtering logic in overrides of this method. @@ -98,16 +97,14 @@ mixin FilterControllerMixin on DisposableController String activeFilterTag() { final activeFilter = _activeFilter.value; - final suffixList = []; - for (final toggleFilter in activeFilter.toggleFilters) { - if (toggleFilter.enabled.value) { - suffixList.add(toggleFilter.name); - } - } - final toggleFilterTag = suffixList.join(','); + final suffixList = activeFilter.settingFilters + .where((f) => f.enabled) + .map((f) => '${f.name}:${f.setting.value}'); + + final settingFilterTag = suffixList.join(','); final queryFilterTag = activeFilter.queryFilter.query.toLowerCase(); return [ - toggleFilterTag, + settingFilterTag, queryFilterTag, if (queryFilterTag.isNotEmpty && useRegExp.value) 'regexp', ].where((e) => e.isNotEmpty).join(filterTagSeparator); @@ -115,8 +112,8 @@ mixin FilterControllerMixin on DisposableController void _resetToDefaultFilter() { // Reset all filter values. - for (final toggleFilter in _toggleFilters) { - toggleFilter.enabled.value = toggleFilter.enabledByDefault; + for (final settingFilter in _settingFilters) { + settingFilter.setting.value = settingFilter.defaultValue; } _queryFilterArgs.forEach((key, value) => value.reset()); } @@ -125,15 +122,15 @@ mixin FilterControllerMixin on DisposableController _resetToDefaultFilter(); _activeFilter.value = Filter( queryFilter: QueryFilter.empty(args: _queryFilterArgs), - toggleFilters: _toggleFilters, + settingFilters: _settingFilters, ); } } -/// Dialog to manage toggleable filter settings. +/// Dialog to manage filter settings. /// /// This dialog interacts with a [FilterControllerMixin] to manage and preserve -/// the toggleable filter state managed by the dialog. +/// the filter state. class FilterDialog extends StatefulWidget { FilterDialog({ super.key, @@ -145,10 +142,10 @@ class FilterDialog extends StatefulWidget { (queryInstructions != null && controller._queryFilterArgs.isNotEmpty), ), - toggleFilterValuesAtOpen = List.generate( - controller.activeFilter.value.toggleFilters.length, + settingFilterValuesAtOpen = List.generate( + controller.activeFilter.value.settingFilters.length, (index) => - controller.activeFilter.value.toggleFilters[index].enabled.value, + controller.activeFilter.value.settingFilters[index].setting.value, ); final FilterControllerMixin controller; @@ -157,7 +154,7 @@ class FilterDialog extends StatefulWidget { final bool includeQueryFilter; - final List toggleFilterValuesAtOpen; + final List settingFilterValuesAtOpen; @override State> createState() => _FilterDialogState(); @@ -222,8 +219,12 @@ class _FilterDialogState extends State> const SizedBox(height: defaultSpacing), ], ], - for (final toggleFilter in widget.controller._toggleFilters) ...[ - ToggleFilterElement(filter: toggleFilter), + for (final filter in widget.controller._settingFilters) ...[ + if (filter is ToggleFilter) + ToggleFilterElement(filter: filter) + else + // TODO(kenz): add a SettingFilterElement widget. + const SizedBox.shrink(), ], ], ), @@ -237,7 +238,7 @@ class _FilterDialogState extends State> query: widget.includeQueryFilter ? queryTextFieldController.value.text : null, - toggleFilters: widget.controller._toggleFilters, + settingFilters: widget.controller._settingFilters, ); } @@ -247,9 +248,9 @@ class _FilterDialogState extends State> } void _restoreOldValues() { - for (var i = 0; i < widget.controller._toggleFilters.length; i++) { - final filter = widget.controller._toggleFilters[i]; - filter.enabled.value = widget.toggleFilterValuesAtOpen[i]; + for (var i = 0; i < widget.controller._settingFilters.length; i++) { + final filter = widget.controller._settingFilters[i]; + filter.setting.value = widget.settingFilterValuesAtOpen[i]; } } } @@ -262,10 +263,10 @@ class ToggleFilterElement extends StatelessWidget { @override Widget build(BuildContext context) { Widget content = InkWell( - onTap: () => filter.enabled.value = !filter.enabled.value, + onTap: () => filter.setting.value = !filter.setting.value, child: Row( children: [ - NotifierCheckbox(notifier: filter.enabled), + NotifierCheckbox(notifier: filter.setting), Text(filter.name), ], ), @@ -281,32 +282,82 @@ class ToggleFilterElement extends StatelessWidget { } class Filter { - Filter({required this.queryFilter, required this.toggleFilters}); + Filter({required this.queryFilter, required this.settingFilters}); final QueryFilter queryFilter; - final List> toggleFilters; + final List> settingFilters; - bool get isEmpty => queryFilter.isEmpty && toggleFilters.isEmpty; + bool get isEmpty => queryFilter.isEmpty && settingFilters.isEmpty; } -class ToggleFilter { +/// A boolean setting filter that can only be set to the value of true or false. +class ToggleFilter extends SettingFilter { ToggleFilter({ + required super.name, + required bool Function(T element) includeCallback, + required super.defaultValue, + super.tooltip, + }) : super( + possibleValues: [true, false], + includeCallback: (T element, bool _) => includeCallback(element), + enabledCallback: (bool filterValue) => filterValue, + ); +} + +/// A filter setting that can be set to any of the predefined values +/// [possibleValues]. +class SettingFilter { + SettingFilter({ required this.name, - required this.includeCallback, + required bool Function(T element, V currentFilterValue) includeCallback, + required bool Function(V filterValue) enabledCallback, + required this.possibleValues, + required this.defaultValue, this.tooltip, - this.enabledByDefault = false, - }) : enabled = ValueNotifier(enabledByDefault); + }) : _includeCallback = includeCallback, + _enabledCallback = enabledCallback, + setting = ValueNotifier(defaultValue), + assert(possibleValues.contains(defaultValue)); + /// The name of this setting filter. final String name; - final bool Function(T element) includeCallback; + /// The set of possible values that [setting] can be set to. + final List possibleValues; + + /// The default value of the filter. + /// + /// This will be used to set the initial [setting] of the filter, and may be set + /// again later if the user triggers "reset to default" behavior from the + /// filter dialog or from some other source. + final V defaultValue; + + /// The current value of this setting filter. + /// + /// Filter dialogs and other filter affordances will read this value and + /// listen to this notifier for changes. + final ValueNotifier setting; + /// The tooltip to describe the setting filter. final String? tooltip; - final bool enabledByDefault; + /// The callback that determines whether a data element should be included + /// based on the filter criteria. + final bool Function(T element, V currentFilterValue) _includeCallback; + + /// The callback that determines whether this filter is enabled based on the + /// current value of the filter. + final bool Function(V filterValue) _enabledCallback; + + /// Whether a data element should be included based on the current state of the + /// filter. + bool includeData(T data) { + return !enabled || _includeCallback(data, setting.value); + } - final ValueNotifier enabled; + /// Whether this filter is enabled based on the current value of the filter. + bool get enabled => _enabledCallback(setting.value); } class QueryFilter { @@ -553,7 +604,7 @@ class _StandaloneFilterFieldState extends State> widget.controller.useRegExp.value = !useRegExp; widget.controller.setActiveFilter( query: queryTextFieldController.value.text, - toggleFilters: widget.controller._toggleFilters, + settingFilters: widget.controller._settingFilters, ); }, ), @@ -561,7 +612,7 @@ class _StandaloneFilterFieldState extends State> onChanged: (_) { widget.controller.setActiveFilter( query: queryTextFieldController.value.text, - toggleFilters: widget.controller._toggleFilters, + settingFilters: widget.controller._settingFilters, ); }, ); diff --git a/packages/devtools_app/test/cpu_profiler/cpu_profiler_controller_test.dart b/packages/devtools_app/test/cpu_profiler/cpu_profiler_controller_test.dart index d1766df2c6c..45c66bfd9c9 100644 --- a/packages/devtools_app/test/cpu_profiler/cpu_profiler_controller_test.dart +++ b/packages/devtools_app/test/cpu_profiler/cpu_profiler_controller_test.dart @@ -32,8 +32,8 @@ void main() { late CpuProfilerController controller; Future disableAllFiltering() async { - for (final filter in controller.activeFilter.value.toggleFilters) { - filter.enabled.value = false; + for (final filter in controller.activeFilter.value.settingFilters) { + filter.setting.value = false; } controller.setActiveFilter(); // [CpuProfilerController.filterData], which is triggered by the call to @@ -172,8 +172,8 @@ void main() { filteredData = controller.dataNotifier.value!; expect(filteredData.stackFrames.values.length, equals(7)); - for (final filter in controller.activeFilter.value.toggleFilters) { - filter.enabled.value = false; + for (final filter in controller.activeFilter.value.settingFilters) { + filter.setting.value = false; } controller.setActiveFilter(query: 'paint thread'); await shortDelay(); @@ -418,7 +418,7 @@ void main() { test('processDataForTag applies toggle filters by default', () async { expect( - controller.activeFilter.value.toggleFilters[0].enabled.value, + controller.activeFilter.value.settingFilters[0].setting.value, isTrue, ); final cpuProfileDataWithTags = diff --git a/packages/devtools_app/test/cpu_profiler/cpu_profiler_test.dart b/packages/devtools_app/test/cpu_profiler/cpu_profiler_test.dart index 3bf5ad88ebf..8651d05c6dd 100644 --- a/packages/devtools_app/test/cpu_profiler/cpu_profiler_test.dart +++ b/packages/devtools_app/test/cpu_profiler/cpu_profiler_test.dart @@ -145,8 +145,8 @@ void main() { Future loadData() async { for (final filter in controller - .cpuProfilerController.activeFilter.value.toggleFilters) { - filter.enabled.value = false; + .cpuProfilerController.activeFilter.value.settingFilters) { + filter.setting.value = false; } final data = CpuProfilePair( functionProfile: cpuProfileData, @@ -599,8 +599,8 @@ void main() { cpuProfileData = CpuProfileData.fromJson(cpuProfileDataWithUserTagsJson); for (final filter in controller - .cpuProfilerController.activeFilter.value.toggleFilters) { - filter.enabled.value = false; + .cpuProfilerController.activeFilter.value.settingFilters) { + filter.setting.value = false; } final data = CpuProfilePair( functionProfile: cpuProfileData, diff --git a/packages/devtools_app/test/logging/logging_controller_test.dart b/packages/devtools_app/test/logging/logging_controller_test.dart index 8fd1ea2c23f..580a73ec7ef 100644 --- a/packages/devtools_app/test/logging/logging_controller_test.dart +++ b/packages/devtools_app/test/logging/logging_controller_test.dart @@ -150,8 +150,8 @@ void main() { expect(controller.filteredData.value, hasLength(5)); // Test query filters assuming default toggle filters are all enabled. - for (final filter in controller.activeFilter.value.toggleFilters) { - filter.enabled.value = true; + for (final filter in controller.activeFilter.value.settingFilters) { + filter.setting.value = true; } controller.setActiveFilter(query: 'abc'); @@ -188,22 +188,22 @@ void main() { // Test toggle filters. final verboseFlutterFrameworkFilter = - controller.activeFilter.value.toggleFilters[0]; + controller.activeFilter.value.settingFilters[0]; final verboseFlutterServiceFilter = - controller.activeFilter.value.toggleFilters[1]; - final gcFilter = controller.activeFilter.value.toggleFilters[2]; + controller.activeFilter.value.settingFilters[1]; + final gcFilter = controller.activeFilter.value.settingFilters[2]; - verboseFlutterFrameworkFilter.enabled.value = false; + verboseFlutterFrameworkFilter.setting.value = false; controller.setActiveFilter(); expect(controller.data, hasLength(12)); expect(controller.filteredData.value, hasLength(9)); - verboseFlutterServiceFilter.enabled.value = false; + verboseFlutterServiceFilter.setting.value = false; controller.setActiveFilter(); expect(controller.data, hasLength(12)); expect(controller.filteredData.value, hasLength(10)); - gcFilter.enabled.value = false; + gcFilter.setting.value = false; controller.setActiveFilter(); expect(controller.data, hasLength(12)); expect(controller.filteredData.value, hasLength(12)); diff --git a/packages/devtools_app/test/logging/logging_screen_v2/logging_controller_v2_test.dart b/packages/devtools_app/test/logging/logging_screen_v2/logging_controller_v2_test.dart index aa94046ff07..49b4cd77c7c 100644 --- a/packages/devtools_app/test/logging/logging_screen_v2/logging_controller_v2_test.dart +++ b/packages/devtools_app/test/logging/logging_screen_v2/logging_controller_v2_test.dart @@ -52,7 +52,7 @@ void main() { expect(controller.loggingModel.filteredLogCount, 0); expect(controller.loggingModel.selectedLogCount, 0); expect( - controller.loggingModel.activeFilter.value.toggleFilters.length, + controller.loggingModel.activeFilter.value.settingFilters.length, 3, ); expect( diff --git a/packages/devtools_app/test/logging/logging_screen_v2/logging_model_test.dart b/packages/devtools_app/test/logging/logging_screen_v2/logging_model_test.dart index 1a284149fd7..0201c1c7b76 100644 --- a/packages/devtools_app/test/logging/logging_screen_v2/logging_model_test.dart +++ b/packages/devtools_app/test/logging/logging_screen_v2/logging_model_test.dart @@ -485,8 +485,9 @@ void main() { expect(loggingTableModel.filteredLogCount, 5); // Test query filters assuming default toggle filters are all enabled. - for (final filter in loggingTableModel.activeFilter.value.toggleFilters) { - filter.enabled.value = true; + for (final filter + in loggingTableModel.activeFilter.value.settingFilters) { + filter.setting.value = true; } loggingTableModel.setActiveFilter(query: 'abc'); @@ -523,22 +524,22 @@ void main() { // Test toggle filters. final verboseFlutterFrameworkFilter = - loggingTableModel.activeFilter.value.toggleFilters[0]; + loggingTableModel.activeFilter.value.settingFilters[0]; final verboseFlutterServiceFilter = - loggingTableModel.activeFilter.value.toggleFilters[1]; - final gcFilter = loggingTableModel.activeFilter.value.toggleFilters[2]; + loggingTableModel.activeFilter.value.settingFilters[1]; + final gcFilter = loggingTableModel.activeFilter.value.settingFilters[2]; - verboseFlutterFrameworkFilter.enabled.value = false; + verboseFlutterFrameworkFilter.setting.value = false; loggingTableModel.setActiveFilter(); expect(loggingTableModel.logCount, 12); expect(loggingTableModel.filteredLogCount, 9); - verboseFlutterServiceFilter.enabled.value = false; + verboseFlutterServiceFilter.setting.value = false; loggingTableModel.setActiveFilter(); expect(loggingTableModel.logCount, 12); expect(loggingTableModel.filteredLogCount, 10); - gcFilter.enabled.value = false; + gcFilter.setting.value = false; loggingTableModel.setActiveFilter(); expect(loggingTableModel.logCount, 12); expect(loggingTableModel.filteredLogCount, 12); diff --git a/packages/devtools_app/test/shared/filter_test.dart b/packages/devtools_app/test/shared/filter_test.dart index 295a724885c..0d5ee17fada 100644 --- a/packages/devtools_app/test/shared/filter_test.dart +++ b/packages/devtools_app/test/shared/filter_test.dart @@ -12,11 +12,8 @@ void main() { void verifyBaseFilterState() { final activeFilter = controller.activeFilter.value; - for (final toggleFilter in activeFilter.toggleFilters) { - expect( - toggleFilter.enabled.value, - equals(toggleFilter.enabledByDefault), - ); + for (final filter in activeFilter.settingFilters) { + expect(filter.setting.value, equals(filter.defaultValue)); } expect(activeFilter.queryFilter.isEmpty, isTrue); } @@ -32,22 +29,20 @@ void main() { // Verify the default state of the active filter. final activeFilter = controller.activeFilter.value; expect(activeFilter.queryFilter.isEmpty, isTrue); - expect(activeFilter.toggleFilters.length, equals(2)); + expect(activeFilter.settingFilters.length, equals(3)); controller.setActiveFilter(); expect( controller.filteredData.value.toString(), equals( - '[1-FooBar-foobar, 3-Baz-foobar, 5-Basset Hound-dog, 7-Shepherd\'s pie-food, 9-Meal bar-food]', + '[1-FooBar-foobar-3, 3-Baz-foobar-5, 5-Basset Hound-dog-3, 9-Meal bar-food-3]', ), ); }); test('filterData applies query and toggle filters', () { // Disable all toggle filters. - for (final toggleFilter in controller.activeFilter.value.toggleFilters) { - toggleFilter.enabled.value = false; - } + controller.disableAllSettingFilters(); expect(controller.useRegExp.value, isFalse); controller.setActiveFilter(); @@ -61,53 +56,55 @@ void main() { expect( controller.filteredData.value.toString(), equals( - '[1-FooBar-foobar, 2-Bar-foobar, 3-Baz-foobar, 5-Basset Hound-dog, 9-Meal bar-food]', + '[1-FooBar-foobar-3, 2-Bar-foobar-4, 3-Baz-foobar-5, 5-Basset Hound-dog-3, 9-Meal bar-food-3]', ), ); controller.setActiveFilter(query: 'Ba cat:foobar'); expect( controller.filteredData.value.toString(), - equals('[1-FooBar-foobar, 2-Bar-foobar, 3-Baz-foobar]'), + equals('[1-FooBar-foobar-3, 2-Bar-foobar-4, 3-Baz-foobar-5]'), ); controller.setActiveFilter(query: 'Baz foo cat:foobar'); expect( controller.filteredData.value.toString(), - equals('[0-Foo-foobar, 1-FooBar-foobar, 3-Baz-foobar]'), + equals('[0-Foo-foobar-2, 1-FooBar-foobar-3, 3-Baz-foobar-5]'), ); // Ain't nothin' but a hound dog controller.setActiveFilter(query: 'Basset'); expect( controller.filteredData.value.toString(), - equals('[5-Basset Hound-dog]'), + equals('[5-Basset Hound-dog-3]'), ); // Only toggle filter. - controller.toggleFilters[1].enabled.value = true; + controller.settingFilters[2].setting.value = true; controller.setActiveFilter( - toggleFilters: controller.toggleFilters, + settingFilters: controller.settingFilters, ); expect( controller.filteredData.value.toString(), equals( - '[1-FooBar-foobar, 2-Bar-foobar, 4-Shepherd-dog, 5-Basset Hound-dog, 7-Shepherd\'s pie-food, 8-Orange-food]', + '[1-FooBar-foobar-3, 2-Bar-foobar-4, 4-Shepherd-dog-1, 5-Basset Hound-dog-3, 7-Shepherd\'s pie-food-1, 8-Orange-food-2]', ), ); // Query and toggle filter. - controller.toggleFilters[0].enabled.value = true; - controller.toggleFilters[1].enabled.value = true; + controller.settingFilters[0].setting.value = 2; + controller.settingFilters[1].setting.value = true; + controller.settingFilters[2].setting.value = true; controller.setActiveFilter(query: 'Ba cat:foobar'); expect( controller.filteredData.value.toString(), - equals('[1-FooBar-foobar]'), + equals('[1-FooBar-foobar-3]'), ); // Excessive filter returns empty list. - controller.toggleFilters[0].enabled.value = false; - controller.toggleFilters[1].enabled.value = false; + controller.settingFilters[0].setting.value = 5; + controller.settingFilters[1].setting.value = false; + controller.settingFilters[2].setting.value = false; controller.setActiveFilter(query: 'abcdefg'); expect( controller.filteredData.value.toString(), @@ -117,9 +114,7 @@ void main() { test('filterData applies regexp query filters when enabled', () { // Disable all toggle filters. - for (final toggleFilter in controller.activeFilter.value.toggleFilters) { - toggleFilter.enabled.value = false; - } + controller.disableAllSettingFilters(); controller.useRegExp.value = true; controller.setActiveFilter(); @@ -133,14 +128,14 @@ void main() { expect( controller.filteredData.value.toString(), equals( - '[0-Foo-foobar, 1-FooBar-foobar, 2-Bar-foobar, 3-Baz-foobar, 7-Shepherd\'s pie-food, 8-Orange-food, 9-Meal bar-food]', + '[0-Foo-foobar-2, 1-FooBar-foobar-3, 2-Bar-foobar-4, 3-Baz-foobar-5, 7-Shepherd\'s pie-food-1, 8-Orange-food-2, 9-Meal bar-food-3]', ), ); controller.setActiveFilter(query: '-cat:foo.*'); expect( controller.filteredData.value.toString(), equals( - '[4-Shepherd-dog, 5-Basset Hound-dog, 6-Husky-dog]', + '[4-Shepherd-dog-1, 5-Basset Hound-dog-3, 6-Husky-dog-5]', ), ); // Regexp substring match. @@ -148,7 +143,7 @@ void main() { expect( controller.filteredData.value.toString(), equals( - '[1-FooBar-foobar, 2-Bar-foobar, 9-Meal bar-food]', + '[1-FooBar-foobar-3, 2-Bar-foobar-4, 9-Meal bar-food-3]', ), ); @@ -170,32 +165,42 @@ void main() { }); test('isFilterActive', () { - controller.toggleFilters[0].enabled.value = true; - controller.toggleFilters[1].enabled.value = false; + controller.settingFilters[0].setting.value = 1; + controller.settingFilters[1].setting.value = true; + controller.settingFilters[2].setting.value = false; + controller.setActiveFilter(); + expect(controller.isFilterActive, true); + + controller.settingFilters[0].setting.value = 1; + controller.settingFilters[1].setting.value = false; + controller.settingFilters[2].setting.value = true; controller.setActiveFilter(); - expect(controller.isFilterActive, isTrue); + expect(controller.isFilterActive, true); - controller.toggleFilters[0].enabled.value = false; - controller.toggleFilters[1].enabled.value = true; + controller.settingFilters[0].setting.value = 2; + controller.settingFilters[1].setting.value = false; + controller.settingFilters[2].setting.value = false; controller.setActiveFilter(); - expect(controller.isFilterActive, isTrue); + expect(controller.isFilterActive, true); - controller.toggleFilters[0].enabled.value = false; - controller.toggleFilters[1].enabled.value = false; + controller.settingFilters[0].setting.value = 1; + controller.settingFilters[1].setting.value = false; + controller.settingFilters[2].setting.value = false; controller.setActiveFilter(); - expect(controller.isFilterActive, equals(false)); + expect(controller.isFilterActive, false); controller.setActiveFilter(query: 'bar'); - expect(controller.isFilterActive, isTrue); + expect(controller.isFilterActive, true); controller.setActiveFilter(query: 'cat:foobar'); - expect(controller.isFilterActive, isTrue); + expect(controller.isFilterActive, true); }); test('activeFilterTag', () { // No filters active. - controller.toggleFilters[0].enabled.value = false; - controller.toggleFilters[1].enabled.value = false; + controller.settingFilters[0].setting.value = 1; + controller.settingFilters[1].setting.value = false; + controller.settingFilters[2].setting.value = false; controller.setActiveFilter(); expect(controller.activeFilterTag(), equals('')); @@ -210,29 +215,54 @@ void main() { controller.useRegExp.value = false; // Only toggle filter active and no query filters. - controller.toggleFilters[0].enabled.value = true; + controller.settingFilters[0].setting.value = 3; controller.setActiveFilter(); - expect(controller.activeFilterTag(), equals('Hide multiples of 2')); + expect( + controller.activeFilterTag(), + equals('Hide items below the minimum rating level:3'), + ); + + controller.settingFilters[1].setting.value = true; + controller.setActiveFilter(); + expect( + controller.activeFilterTag(), + equals( + 'Hide items below the minimum rating level:3,' + 'Hide multiples of 2:true', + ), + ); - controller.toggleFilters[1].enabled.value = true; + controller.settingFilters[2].setting.value = true; controller.setActiveFilter(); expect( controller.activeFilterTag(), - equals('Hide multiples of 2,Hide multiples of 3'), + equals( + 'Hide items below the minimum rating level:3,' + 'Hide multiples of 2:true,' + 'Hide multiples of 3:true', + ), ); - controller.toggleFilters[0].enabled.value = false; + controller.settingFilters[1].setting.value = false; controller.setActiveFilter(); - expect(controller.activeFilterTag(), equals('Hide multiples of 3')); + expect( + controller.activeFilterTag(), + equals( + 'Hide items below the minimum rating level:3,' + 'Hide multiples of 3:true', + ), + ); // Both query filter and toggle filters active. - controller.toggleFilters[0].enabled.value = true; - controller.toggleFilters[1].enabled.value = true; + controller.settingFilters[1].setting.value = true; controller.setActiveFilter(query: 'Ba cat:foobar'); expect( controller.activeFilterTag(), equals( - 'Hide multiples of 2,Hide multiples of 3-#-ba cat:foobar', + 'Hide items below the minimum rating level:3,' + 'Hide multiples of 2:true,' + 'Hide multiples of 3:true' + '-#-ba cat:foobar', ), ); }); @@ -242,11 +272,12 @@ void main() { controller.resetFilter(); verifyBaseFilterState(); - controller.toggleFilters[0].enabled.value = true; - controller.toggleFilters[1].enabled.value = true; + controller.settingFilters[0].setting.value = 5; + controller.settingFilters[1].setting.value = true; + controller.settingFilters[2].setting.value = true; controller.setActiveFilter(query: 'cat:foobar'); - for (final toggleFilter in controller.toggleFilters) { - expect(toggleFilter.enabled.value, isTrue); + for (final settingFilter in controller.settingFilters) { + expect(settingFilter.enabled, isTrue); } expect(controller.activeFilter.value.queryFilter.isEmpty, isFalse); @@ -265,19 +296,28 @@ class _TestController extends DisposableController final List<_TestDataClass> data; // Convenience getters for testing. - List> get toggleFilters => - activeFilter.value.toggleFilters; + List> get settingFilters => + activeFilter.value.settingFilters; @override - List> createToggleFilters() => [ + List> createSettingFilters() => [ + SettingFilter<_TestDataClass, int>( + name: 'Hide items below the minimum rating level', + includeCallback: (_TestDataClass element, int currentFilterValue) => + element.rating >= currentFilterValue, + enabledCallback: (int filterValue) => filterValue > 1, + possibleValues: [1, 2, 3, 4, 5], + defaultValue: 2, + ), ToggleFilter<_TestDataClass>( name: 'Hide multiples of 2', includeCallback: (data) => data.id % 2 != 0, - enabledByDefault: true, + defaultValue: true, ), ToggleFilter<_TestDataClass>( name: 'Hide multiples of 3', includeCallback: (data) => data.id % 3 != 0, + defaultValue: false, ), ]; @@ -303,12 +343,10 @@ class _TestController extends DisposableController } bool filterCallback(_TestDataClass element) { // First filter by the toggle filters. - final filteredOutByToggleFilters = filter.toggleFilters.any( - (toggleFilter) => - toggleFilter.enabled.value && - !toggleFilter.includeCallback(element), + final filteredOutBySettingFilters = filter.settingFilters.any( + (settingFilter) => !settingFilter.includeData(element), ); - if (filteredOutByToggleFilters) return false; + if (filteredOutBySettingFilters) return false; final queryFilter = filter.queryFilter; if (!queryFilter.isEmpty) { @@ -340,30 +378,42 @@ class _TestController extends DisposableController ..clear() ..addAll(data.where(filterCallback)); } + + void disableAllSettingFilters() { + for (final filter in activeFilter.value.settingFilters) { + if (filter is ToggleFilter) { + filter.setting.value = false; + } else { + // This is the lowest setting for the integer setting filter. + filter.setting.value = 1; + } + } + } } class _TestDataClass { - const _TestDataClass(this.id, this.label, this.category); + const _TestDataClass(this.id, this.label, this.category, this.rating); final int id; final String label; final String category; + final int rating; @override String toString() { - return [id.toString(), label, category].join('-'); + return [id.toString(), label, category, rating].join('-'); } } const _sampleData = [ - _TestDataClass(0, 'Foo', 'foobar'), - _TestDataClass(1, 'FooBar', 'foobar'), - _TestDataClass(2, 'Bar', 'foobar'), - _TestDataClass(3, 'Baz', 'foobar'), - _TestDataClass(4, 'Shepherd', 'dog'), - _TestDataClass(5, 'Basset Hound', 'dog'), - _TestDataClass(6, 'Husky', 'dog'), - _TestDataClass(7, 'Shepherd\'s pie', 'food'), - _TestDataClass(8, 'Orange', 'food'), - _TestDataClass(9, 'Meal bar', 'food'), + _TestDataClass(0, 'Foo', 'foobar', 2), + _TestDataClass(1, 'FooBar', 'foobar', 3), + _TestDataClass(2, 'Bar', 'foobar', 4), + _TestDataClass(3, 'Baz', 'foobar', 5), + _TestDataClass(4, 'Shepherd', 'dog', 1), + _TestDataClass(5, 'Basset Hound', 'dog', 3), + _TestDataClass(6, 'Husky', 'dog', 5), + _TestDataClass(7, 'Shepherd\'s pie', 'food', 1), + _TestDataClass(8, 'Orange', 'food', 2), + _TestDataClass(9, 'Meal bar', 'food', 3), ]; diff --git a/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart b/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart index 010ecc294ce..4aa8e5842e6 100644 --- a/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart +++ b/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart @@ -192,7 +192,7 @@ MockLoggingController createMockLoggingControllerWithDefaults({ final activeFilter = FixedValueListenable( Filter( queryFilter: QueryFilter.empty(args: LoggingController.queryFilterArgs), - toggleFilters: LoggingController.toggleFilters, + settingFilters: LoggingController.settingFilters, ), ); provideDummy>>(activeFilter);