import 'package:flutter_test/flutter_test.dart'; import 'package:worldhopper/models/entry.dart'; import 'package:worldhopper/models/feed.dart'; import 'package:worldhopper/providers/series_navigation_provider.dart'; Entry _entry(String id, String title) => Entry(id: id, title: title); Feed _feedWith(List entries) => Feed(id: 'feed-1', title: 'Series', entries: entries); /// Extracts the next-in-series logic from the provider for unit testing. /// This mirrors the logic in nextInSeriesProvider without needing Riverpod. Entry? _findNextInSeries(Feed feed, String currentEntryId) { final entries = feed.entries; for (var i = 0; i < entries.length; i++) { if (entries[i].id == currentEntryId) { if (i + 1 < entries.length) { return entries[i + 1]; } return null; } } return null; } void main() { group('nextInSeriesProvider', () { test('returns next entry when current is in the middle', () { final feed = _feedWith([ _entry('ch1', 'Chapter 1'), _entry('ch2', 'Chapter 2'), _entry('ch3', 'Chapter 3'), ]); final result = _findNextInSeries(feed, 'ch1'); expect(result, isNotNull); expect(result!.id, 'ch2'); expect(result.title, 'Chapter 2'); }); test('returns next entry when current is second-to-last', () { final feed = _feedWith([ _entry('ch1', 'Chapter 1'), _entry('ch2', 'Chapter 2'), _entry('ch3', 'Chapter 3'), ]); final result = _findNextInSeries(feed, 'ch2'); expect(result, isNotNull); expect(result!.id, 'ch3'); }); test('returns null when current entry is the last', () { final feed = _feedWith([ _entry('ch1', 'Chapter 1'), _entry('ch2', 'Chapter 2'), _entry('ch3', 'Chapter 3'), ]); final result = _findNextInSeries(feed, 'ch3'); expect(result, isNull); }); test('returns null when current entry is not found in feed', () { final feed = _feedWith([ _entry('ch1', 'Chapter 1'), _entry('ch2', 'Chapter 2'), ]); final result = _findNextInSeries(feed, 'unknown-id'); expect(result, isNull); }); test('returns null for an empty feed', () { final feed = _feedWith([]); final result = _findNextInSeries(feed, 'ch1'); expect(result, isNull); }); test('returns null for single-entry feed', () { final feed = _feedWith([_entry('ch1', 'Only Chapter')]); final result = _findNextInSeries(feed, 'ch1'); 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))); }); }); }