perf: isolate focus and media rebuilds

This commit is contained in:
edde746
2026-07-12 19:00:11 +02:00
parent a561777456
commit 2101f1d7f0
28 changed files with 771 additions and 499 deletions
+54
View File
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/focus/input_mode_tracker.dart';
import 'package:plezy/services/gamepad_service.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
testWidgets('one-shot reads do not subscribe to input-mode changes', (tester) async {
var listeningBuilds = 0;
var oneShotBuilds = 0;
InputMode? listeningMode;
InputMode? oneShotMode;
await tester.pumpWidget(
InputModeTracker(
child: Directionality(
textDirection: TextDirection.ltr,
child: Row(
children: [
Builder(
builder: (context) {
listeningBuilds++;
listeningMode = InputModeTracker.of(context);
return const SizedBox.shrink();
},
),
Builder(
builder: (context) {
oneShotBuilds++;
oneShotMode = InputModeTracker.of(context, listen: false);
return const SizedBox.shrink();
},
),
],
),
),
),
);
expect(listeningMode, InputMode.pointer);
expect(oneShotMode, InputMode.pointer);
expect(listeningBuilds, 1);
expect(oneShotBuilds, 1);
GamepadService.onGamepadInput!.call();
await tester.pump();
expect(listeningMode, InputMode.keyboard);
expect(listeningBuilds, 2);
expect(oneShotMode, InputMode.pointer);
expect(oneShotBuilds, 1);
});
}
@@ -89,9 +89,13 @@ class _FakeConnectionRegistry extends ConnectionRegistry {
_FakeConnectionRegistry(super.db, this.connections);
final List<Connection> connections;
int watchCalls = 0;
@override
Stream<List<Connection>> watchConnections() => Stream.value(connections);
Stream<List<Connection>> watchConnections() {
watchCalls++;
return Stream.value(connections);
}
}
void main() {
@@ -102,7 +106,7 @@ void main() {
late DownloadManagerService downloadManager;
late MultiServerManager serverManager;
MultiServerProvider? multiServerProvider;
late ConnectionRegistry connectionRegistry;
late _FakeConnectionRegistry connectionRegistry;
late List<Connection> connections;
setUp(() async {
@@ -251,6 +255,18 @@ void main() {
expect(find.text('No sync rules'), findsOneWidget);
});
testWidgets('provider rebuilds reuse the connection stream subscription', (tester) async {
multiServerProvider = MultiServerProvider(serverManager, DataAggregationService(serverManager));
await insertRule(ServerId('orphan-srv'), '76672');
await pumpScreen(tester);
expect(connectionRegistry.watchCalls, 1);
await downloadProvider.updateSyncRuleCount(downloadProvider.syncRules.keys.single, 6);
await tester.pump();
expect(connectionRegistry.watchCalls, 1);
});
testWidgets('does not autofocus the first sync rule in pointer mode', (tester) async {
multiServerProvider = MultiServerProvider(serverManager, DataAggregationService(serverManager));
await insertRule(ServerId('orphan-srv'), '76672');
@@ -4,6 +4,7 @@ import 'package:plezy/media/ids.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:plezy/i18n/strings.g.dart';
import 'package:plezy/media/media_backend.dart';
import 'package:plezy/media/media_kind.dart';
@@ -516,4 +517,41 @@ void main() {
expect(selectedLibraryKey, hiddenServerALibrary.globalKey);
});
testWidgets('rail item focus repaints locally without rebuilding its parent', (tester) async {
final focusNode = FocusNode();
addTearDown(focusNode.dispose);
var parentBuilds = 0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(extensions: const [_testTokens]),
home: Scaffold(
body: Builder(
builder: (context) {
parentBuilds++;
return NavigationRailItem(
icon: Symbols.home_rounded,
label: const Text('Home'),
isSelected: false,
onTap: () {},
focusNode: focusNode,
);
},
),
),
),
);
final item = find.byType(NavigationRailItem);
expect(_railItemDecoration(tester, item)?.color, isNull);
expect(parentBuilds, 1);
focusNode.requestFocus();
await tester.pump();
expect(focusNode.hasFocus, isTrue);
expect(_railItemDecoration(tester, item)?.color, isNotNull);
expect(parentBuilds, 1);
});
}