fix(jellyfin): order continue watching + up next by recency
This commit is contained in:
@@ -1939,6 +1939,143 @@ void main() {
|
||||
expect(nextUp.queryParameters.containsKey('NextUpDateCutoff'), isFalse);
|
||||
});
|
||||
|
||||
test('fetchContinueWatching orders a recently watched series Next Up above an older resume item', () async {
|
||||
final requests = <Uri>[];
|
||||
final scoped = JellyfinClient.forTesting(
|
||||
connection: _conn(),
|
||||
httpClient: MockClient((req) async {
|
||||
requests.add(req.url);
|
||||
if (req.url.path == '/UserItems/Resume') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'Items': [
|
||||
{
|
||||
'Id': 'resume-old',
|
||||
'Type': 'Movie',
|
||||
'Name': 'Old Movie',
|
||||
'UserData': {'LastPlayedDate': '2020-01-01T00:00:00.0000000Z'},
|
||||
},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
if (req.url.path == '/Shows/NextUp') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'Items': [
|
||||
{'Id': 'next-recent', 'Type': 'Episode', 'Name': 'Next Recent', 'SeriesId': 'show-recent'},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
if (req.url.path == '/Items') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'Items': [
|
||||
{
|
||||
'Id': 'ep-played',
|
||||
'Type': 'Episode',
|
||||
'SeriesId': 'show-recent',
|
||||
'UserData': {'LastPlayedDate': '2026-06-01T00:00:00.0000000Z'},
|
||||
},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
return http.Response('not found', 404);
|
||||
}),
|
||||
);
|
||||
addTearDown(scoped.close);
|
||||
|
||||
final items = await scoped.fetchContinueWatching(count: 10);
|
||||
|
||||
// The Next Up episode inherits its series' recent last-played date, so it
|
||||
// sorts above the older resume item (issue #1266).
|
||||
expect(items.map((item) => item.id), ['next-recent', 'resume-old']);
|
||||
|
||||
final lookup = requests.singleWhere((uri) => uri.path == '/Items');
|
||||
expect(lookup.queryParameters['userId'], 'user-1');
|
||||
expect(lookup.queryParameters['IncludeItemTypes'], 'Episode');
|
||||
expect(lookup.queryParameters['Recursive'], 'true');
|
||||
expect(lookup.queryParameters['SortBy'], 'DatePlayed');
|
||||
expect(lookup.queryParameters['SortOrder'], 'Descending');
|
||||
expect(lookup.queryParameters['Limit'], '200');
|
||||
// No Filters=IsPlayed: a series' newest engagement can sit on an episode
|
||||
// with a LastPlayedDate but Played==false (see _attachSeriesLastPlayed).
|
||||
expect(lookup.queryParameters.containsKey('Filters'), isFalse);
|
||||
});
|
||||
|
||||
test('fetchContinueWatching does not let resume items starve Next Up under the limit', () async {
|
||||
final scoped = JellyfinClient.forTesting(
|
||||
connection: _conn(),
|
||||
httpClient: MockClient((req) async {
|
||||
if (req.url.path == '/UserItems/Resume') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'Items': [
|
||||
{
|
||||
'Id': 'resume-old-1',
|
||||
'Type': 'Movie',
|
||||
'Name': 'Old Movie 1',
|
||||
'UserData': {'LastPlayedDate': '2021-01-01T00:00:00.0000000Z'},
|
||||
},
|
||||
{
|
||||
'Id': 'resume-old-2',
|
||||
'Type': 'Movie',
|
||||
'Name': 'Old Movie 2',
|
||||
'UserData': {'LastPlayedDate': '2022-01-01T00:00:00.0000000Z'},
|
||||
},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
if (req.url.path == '/Shows/NextUp') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'Items': [
|
||||
{'Id': 'next-recent', 'Type': 'Episode', 'Name': 'Next Recent', 'SeriesId': 'show-recent'},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
if (req.url.path == '/Items') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'Items': [
|
||||
{
|
||||
'Id': 'ep-played',
|
||||
'Type': 'Episode',
|
||||
'SeriesId': 'show-recent',
|
||||
'UserData': {'LastPlayedDate': '2026-06-01T00:00:00.0000000Z'},
|
||||
},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
return http.Response('not found', 404);
|
||||
}),
|
||||
);
|
||||
addTearDown(scoped.close);
|
||||
|
||||
// count equals the number of resume items: the old resume-first merge would
|
||||
// have filled the limit and dropped Next Up entirely.
|
||||
final items = await scoped.fetchContinueWatching(count: 2);
|
||||
|
||||
expect(items.map((item) => item.id), ['next-recent', 'resume-old-2']);
|
||||
});
|
||||
|
||||
test('fetchContinueWatching keeps resume items when Next Up fails', () async {
|
||||
final scoped = JellyfinClient.forTesting(
|
||||
connection: _conn(),
|
||||
|
||||
Reference in New Issue
Block a user