Files
sm-emby-share/test/features/aggregated/aggregated_grouping_test.dart
T

320 lines
8.9 KiB
Dart
Raw Normal View History

2026-07-14 11:11:36 +08:00
import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/core/contracts/library.dart';
import 'package:smplayer/features/aggregated/aggregated_grouping.dart';
GlobalSearchServerResult _server(
String id,
String name,
List<EmbyRawItem> items,
) => GlobalSearchServerResult(
serverId: id,
serverName: name,
serverUrl: 'http://$id',
token: 'tok-$id',
userId: 'user-$id',
items: items,
);
EmbyRawItem _item(
String id,
String name, {
String? type,
Map<String, dynamic>? providerIds,
String? lastPlayedDate,
int? playbackPositionTicks,
int? runtimeTicks,
bool? played,
}) => EmbyRawItem(
Id: id,
Name: name,
Type: type,
RunTimeTicks: runtimeTicks,
UserData: playbackPositionTicks != null || played != null
? EmbyRawUserData(
PlaybackPositionTicks: playbackPositionTicks,
Played: played,
)
: null,
extra: {
if (providerIds != null) 'ProviderIds': providerIds,
if (lastPlayedDate != null) 'UserData': {'LastPlayedDate': lastPlayedDate},
},
);
void main() {
group('flatten default behavior (dedup off)', () {
test('returns all items from all servers', () {
final results = [
_server('s1', 'S1', [_item('a', 'Movie A')]),
_server('s2', 'S2', [_item('b', 'Movie B')]),
];
final entries = flatten(results);
expect(entries.length, 2);
});
test('respects serverFilter', () {
final results = [
_server('s1', 'S1', [_item('a', 'A')]),
_server('s2', 'S2', [_item('b', 'B')]),
];
final entries = flatten(results, serverFilter: 's1');
expect(entries.length, 1);
expect(entries.first.source.serverId, 's1');
});
});
group('flatten dedup on, serverFilter null', () {
test('collapses same TMDB Movie to newest LastPlayedDate', () {
final results = [
_server('s1', 'S1', [
_item(
'a',
'Movie',
type: 'Movie',
providerIds: {'Tmdb': '100'},
lastPlayedDate: '2026-06-20T10:00:00Z',
),
]),
_server('s2', 'S2', [
_item(
'b',
'Movie',
type: 'Movie',
providerIds: {'Tmdb': '100'},
lastPlayedDate: '2026-06-24T15:00:00Z',
),
]),
];
final entries = flatten(results, deduplicateAcrossServers: true);
expect(entries.length, 1);
expect(entries.first.source.serverId, 's2');
expect(entries.first.sources.map((source) => source.source.serverId), [
's2',
's1',
]);
});
test('preserves each server item and its independent playback state', () {
final results = [
_server('s1', 'S1', [
_item(
'a',
'Movie',
type: 'Movie',
providerIds: {'Tmdb': '100'},
lastPlayedDate: '2026-06-20T10:00:00Z',
playbackPositionTicks: 100,
runtimeTicks: 1000,
),
]),
_server('s2', 'S2', [
_item(
'b',
'Movie',
type: 'Movie',
providerIds: {'Tmdb': '100'},
lastPlayedDate: '2026-06-24T15:00:00Z',
playbackPositionTicks: 800,
runtimeTicks: 1000,
),
]),
];
final entry = flatten(results, deduplicateAcrossServers: true).single;
expect(entry.sources, hasLength(2));
expect(entry.sources[0].item.UserData?.PlaybackPositionTicks, 800);
expect(entry.sources[1].item.UserData?.PlaybackPositionTicks, 100);
expect(entry.sources[0].item.RunTimeTicks, 1000);
expect(entry.sources[1].item.RunTimeTicks, 1000);
});
test('keeps input order when sources have equal LastPlayedDate', () {
final results = [
_server('s1', 'S1', [
_item(
'a',
'Movie',
type: 'Movie',
providerIds: {'Tmdb': '100'},
lastPlayedDate: '2026-06-24T15:00:00Z',
),
]),
_server('s2', 'S2', [
_item(
'b',
'Movie',
type: 'Movie',
providerIds: {'Tmdb': '100'},
lastPlayedDate: '2026-06-24T15:00:00Z',
),
]),
];
final entry = flatten(results, deduplicateAcrossServers: true).single;
expect(entry.sources.map((source) => source.source.serverId), [
's1',
's2',
]);
});
});
group('flatten dedup on + serverFilter non-null', () {
test('skips dedup when server filter is active', () {
final results = [
_server('s1', 'S1', [
_item('a', 'Movie', type: 'Movie', providerIds: {'Tmdb': '100'}),
]),
_server('s2', 'S2', [
_item('b', 'Movie', type: 'Movie', providerIds: {'Tmdb': '100'}),
]),
];
final entries = flatten(
results,
serverFilter: 's1',
deduplicateAcrossServers: true,
);
expect(entries.length, 1);
expect(entries.first.source.serverId, 's1');
expect(entries.first.sources, hasLength(1));
});
});
group('no ProviderIds never deduped', () {
test('items without ProviderIds stay separate', () {
final results = [
_server('s1', 'S1', [_item('a', 'Same Name', type: 'Movie')]),
_server('s2', 'S2', [_item('b', 'Same Name', type: 'Movie')]),
];
final entries = flatten(results, deduplicateAcrossServers: true);
expect(entries.length, 2);
});
});
group('Type namespace isolation', () {
test('Movie and Series with same tmdb id stay separate', () {
final results = [
_server('s1', 'S1', [
_item('a', 'Title', type: 'Movie', providerIds: {'Tmdb': '100'}),
]),
_server('s2', 'S2', [
_item('b', 'Title', type: 'Series', providerIds: {'Tmdb': '100'}),
]),
];
final entries = flatten(results, deduplicateAcrossServers: true);
expect(entries.length, 2);
});
});
group('ProviderIds key case insensitivity', () {
test('Tmdb vs tmdb vs TMDB all match', () {
final results = [
_server('s1', 'S1', [
_item(
'a',
'M',
type: 'Movie',
providerIds: {'Tmdb': '99'},
lastPlayedDate: '2026-06-20T00:00:00Z',
),
]),
_server('s2', 'S2', [
_item(
'b',
'M',
type: 'Movie',
providerIds: {'tmdb': '99'},
lastPlayedDate: '2026-06-21T00:00:00Z',
),
]),
_server('s3', 'S3', [
_item(
'c',
'M',
type: 'Movie',
providerIds: {'TMDB': '99'},
lastPlayedDate: '2026-06-22T00:00:00Z',
),
]),
];
final entries = flatten(results, deduplicateAcrossServers: true);
expect(entries.length, 1);
expect(entries.first.source.serverId, 's3');
});
});
group('null LastPlayedDate handling', () {
test('null loses to valid date', () {
final results = [
_server('s1', 'S1', [
_item('a', 'M', type: 'Movie', providerIds: {'Tmdb': '1'}),
]),
_server('s2', 'S2', [
_item(
'b',
'M',
type: 'Movie',
providerIds: {'Tmdb': '1'},
lastPlayedDate: '2026-06-01T00:00:00Z',
),
]),
];
final entries = flatten(results, deduplicateAcrossServers: true);
expect(entries.length, 1);
expect(entries.first.source.serverId, 's2');
});
test('both null keeps first encountered', () {
final results = [
_server('s1', 'S1', [
_item('a', 'M', type: 'Movie', providerIds: {'Tmdb': '1'}),
]),
_server('s2', 'S2', [
_item('b', 'M', type: 'Movie', providerIds: {'Tmdb': '1'}),
]),
];
final entries = flatten(results, deduplicateAcrossServers: true);
expect(entries.length, 1);
expect(entries.first.source.serverId, 's1');
});
});
group('transitive matching', () {
test('A(tmdb:1) B(tmdb:1,imdb:tt2) C(imdb:tt2) collapse to one', () {
final results = [
_server('s1', 'S1', [
_item(
'a',
'M',
type: 'Movie',
providerIds: {'Tmdb': '1'},
lastPlayedDate: '2026-06-20T00:00:00Z',
),
]),
_server('s2', 'S2', [
_item(
'b',
'M',
type: 'Movie',
providerIds: {'Tmdb': '1', 'Imdb': 'tt2'},
lastPlayedDate: '2026-06-22T00:00:00Z',
),
]),
_server('s3', 'S3', [
_item(
'c',
'M',
type: 'Movie',
providerIds: {'Imdb': 'tt2'},
lastPlayedDate: '2026-06-21T00:00:00Z',
),
]),
];
final entries = flatten(results, deduplicateAcrossServers: true);
expect(entries.length, 1);
expect(entries.first.source.serverId, 's2');
});
});
}