216 lines
5.7 KiB
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)));
|
|
});
|
|
});
|
|
}
|