Files
sync_replay_project/apps/desktop/test/singletons_test.dart
T
foxyzandClaude Opus 5.5 25b19f2d6a Restructure step 5: one media pipeline on a player host
- PlayerHost port (MediaKitHost): players by role, pins, hwdec policy,
  render probe, log routing, disposal order; the only media_kit user.
- MediaPipeline: plan (one pure switch over state, origin, caps,
  presence) -> resolve -> open (prepared player promoted, else fresh)
  -> ready; commands queued during a switch apply once (P15); viewers
  play the buffer and wait for a dropped source without seek loops (D6).
- Every web page resolved by our yt-dlp into an EDL with headers and
  cookies (danmaku excluded); mpv's ytdl_hook no longer used (ytdl=no).
- D11: sign-in errors say which cookies were used and whether they hold
  any for the site.
- Relay: 256 KiB slices read as they fill, superseded pulls released
  (seek-to-play ~4.2 s -> ~2.8 s, P29); own Jellyfin relay channel only
  when someone needs it (P28).
- STARSYNC_SMOKE run and a Windows CI smoke step after the build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 01:08:54 +03:00

48 lines
1.7 KiB
Dart

import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
/// The app's own `.instance` singletons and how often `lib` reaches for
/// each. Systems get their dependencies through the composition root
/// (`AppGraph`, widgets through `AppScope`); this list may only shrink.
/// Lower a number when a use goes; never raise one or add a class.
const baseline = {
'AppSettings': 55,
'JellyfinAccounts': 10,
'UpdateController': 8,
'Profile': 8,
'FolderStore': 6,
'AppYtDlp': 2,
'RecentLinks': 3,
};
void main() {
test('no new .instance singletons, and no more uses of the old ones', () {
final files = Directory('lib')
.listSync(recursive: true)
.whereType<File>()
.where((f) => f.path.endsWith('.dart'))
.toList();
final sources = [for (final f in files) f.readAsStringSync()];
// The app's own classes (framework singletons like
// `WidgetsBinding.instance` are not counted).
final classes = {
for (final s in sources)
for (final m in RegExp(r'^(?:abstract |final |base )*class (\w+)', multiLine: true).allMatches(s)) m.group(1)!,
};
final counts = <String, int>{};
for (final s in sources) {
for (final m in RegExp(r'\b([A-Z]\w*)\.instance\b').allMatches(s)) {
final name = m.group(1)!;
if (classes.contains(name)) counts[name] = (counts[name] ?? 0) + 1;
}
}
final grown = {
for (final e in counts.entries)
if (e.value > (baseline[e.key] ?? 0)) e.key: '${e.value} (baseline ${baseline[e.key] ?? 'none'})',
};
expect(grown, isEmpty,
reason: 'pass the system in through AppGraph / AppScope instead of a new `.instance` use or singleton');
});
}