worldhopper/test/providers/series_navigation_provider_test.dart

216 lines
5.7 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:worldhopper/models/opds_entry.dart';
import 'package:worldhopper/models/opds_feed.dart';
import 'package:worldhopper/providers/opds_provider.dart';
import 'package:worldhopper/providers/series_navigation_provider.dart';
import 'package:worldhopper/repositories/opds_repository.dart';
/// Fake repository that returns a canned feed for any request.
class FakeOPDSRepository extends OPDSRepository {
final OPDSFeed fakeFeed;
FakeOPDSRepository(this.fakeFeed);
@override
Future<OPDSFeed> fetchFeed(
String serverId,
String url, {
bool forceRefresh = false,
CancelToken? cancelToken,
}) async {
return fakeFeed;
}
}
OPDSEntry _entry(String id, String title) =>
OPDSEntry(id: id, title: title);
OPDSFeed _feedWith(List<OPDSEntry> entries) =>
OPDSFeed(id: 'feed-1', title: 'Series', entries: entries);
void main() {
group('nextInSeriesProvider', () {
test('returns next entry when current is in the middle', () async {
final entries = [
_entry('ch1', 'Chapter 1'),
_entry('ch2', 'Chapter 2'),
_entry('ch3', 'Chapter 3'),
];
final container = ProviderContainer(
overrides: [
opdsRepositoryProvider
.overrideWithValue(FakeOPDSRepository(_feedWith(entries))),
],
);
addTearDown(container.dispose);
final result = await container.read(
nextInSeriesProvider(const NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch1',
)).future,
);
expect(result, isNotNull);
expect(result!.id, 'ch2');
expect(result.title, 'Chapter 2');
});
test('returns next entry when current is second-to-last', () async {
final entries = [
_entry('ch1', 'Chapter 1'),
_entry('ch2', 'Chapter 2'),
_entry('ch3', 'Chapter 3'),
];
final container = ProviderContainer(
overrides: [
opdsRepositoryProvider
.overrideWithValue(FakeOPDSRepository(_feedWith(entries))),
],
);
addTearDown(container.dispose);
final result = await container.read(
nextInSeriesProvider(const NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch2',
)).future,
);
expect(result, isNotNull);
expect(result!.id, 'ch3');
});
test('returns null when current entry is the last', () async {
final entries = [
_entry('ch1', 'Chapter 1'),
_entry('ch2', 'Chapter 2'),
_entry('ch3', 'Chapter 3'),
];
final container = ProviderContainer(
overrides: [
opdsRepositoryProvider
.overrideWithValue(FakeOPDSRepository(_feedWith(entries))),
],
);
addTearDown(container.dispose);
final result = await container.read(
nextInSeriesProvider(const NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch3',
)).future,
);
expect(result, isNull);
});
test('returns null when current entry is not found in feed', () async {
final entries = [
_entry('ch1', 'Chapter 1'),
_entry('ch2', 'Chapter 2'),
];
final container = ProviderContainer(
overrides: [
opdsRepositoryProvider
.overrideWithValue(FakeOPDSRepository(_feedWith(entries))),
],
);
addTearDown(container.dispose);
final result = await container.read(
nextInSeriesProvider(const NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'unknown-id',
)).future,
);
expect(result, isNull);
});
test('returns null for an empty feed', () async {
final container = ProviderContainer(
overrides: [
opdsRepositoryProvider
.overrideWithValue(FakeOPDSRepository(_feedWith([]))),
],
);
addTearDown(container.dispose);
final result = await container.read(
nextInSeriesProvider(const NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch1',
)).future,
);
expect(result, isNull);
});
test('returns null for single-entry feed', () async {
final entries = [_entry('ch1', 'Only Chapter')];
final container = ProviderContainer(
overrides: [
opdsRepositoryProvider
.overrideWithValue(FakeOPDSRepository(_feedWith(entries))),
],
);
addTearDown(container.dispose);
final result = await container.read(
nextInSeriesProvider(const NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch1',
)).future,
);
expect(result, isNull);
});
});
group('NextInSeriesRequest equality', () {
test('equal requests have same hashCode', () {
const a = NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch1',
);
const b = NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch1',
);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
});
test('different requests are not equal', () {
const a = NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch1',
);
const b = NextInSeriesRequest(
serverId: 's1',
feedUrl: '/feed',
currentEntryId: 'ch2',
);
expect(a, isNot(equals(b)));
});
});
}