import 'package:flutter_test/flutter_test.dart'; import 'package:rippr/src/geo/geo.dart' show LatLon; import 'package:rippr/src/tiles/tile_math.dart'; void main() { group('tilesForBounds', () { test('a single point covers exactly one tile per zoom level', () { final tiles = tilesForBounds( minLat: 51.0, maxLat: 51.0, minLon: -114.0, maxLon: -114.0, minZoom: 10, maxZoom: 12, ); // One tile at each of the three zoom levels. expect(tiles.length, 3); expect(tiles.map((t) => t.z).toSet(), {10, 11, 12}); }); test('the same box covers more tiles at a higher zoom', () { final low = tilesForBounds( minLat: 50.99, maxLat: 51.01, minLon: -114.01, maxLon: -113.99, minZoom: 10, maxZoom: 10, ); final high = tilesForBounds( minLat: 50.99, maxLat: 51.01, minLon: -114.01, maxLon: -113.99, minZoom: 14, maxZoom: 14, ); expect(high.length, greaterThan(low.length)); }); test('throws rather than silently truncating past the cap', () { expect( () => tilesForBounds( minLat: -80, maxLat: 80, minLon: -170, maxLon: 170, minZoom: 10, maxZoom: 14, ), throwsA(isA()), ); }); test('tiles are deduplicated across an overlapping request', () { final tiles = tilesForBounds( minLat: 51.0, maxLat: 51.001, minLon: -114.0, maxLon: -113.999, minZoom: 5, maxZoom: 5, ); // A Set literally cannot contain a duplicate; this documents the intent that // adjacent/overlapping coverage collapses rather than being counted twice. expect(tiles.toSet().length, tiles.length); }); }); group('tilesAlongRoute', () { test('costs far less than the bounding-box rectangle for a route that ' 'zigzags across its own bounding box', () { // A route that ping-pongs in latitude while advancing in longitude -- its // bounding box is a full rectangle, but the corridor only has to cover the // narrow path actually ridden through that rectangle. final route = [ for (var i = 0; i <= 40; i++) LatLon(51.0 + (i.isEven ? 0.0 : 0.3), -114.0 + i * 0.02), ]; final corridor = tilesAlongRoute( route, minZoom: 12, maxZoom: 12, bufferMeters: 150, ); final rectangle = tilesForBounds( minLat: 51.0, maxLat: 51.3, minLon: -114.0, maxLon: -113.2, minZoom: 12, maxZoom: 12, ); expect(corridor.length, lessThan(rectangle.length), reason: 'the whole point of a corridor is fewer tiles than the bounding box'); }); test('an empty route needs no tiles', () { expect(tilesAlongRoute([], minZoom: 10, maxZoom: 12), isEmpty); }); test('throws past the cap, same as tilesForBounds', () { final route = [for (var i = 0; i < 500; i++) LatLon(i * 0.1 - 25, i * 0.1 - 25)]; expect( () => tilesAlongRoute(route, minZoom: 12, maxZoom: 14, bufferMeters: 500), throwsA(isA()), ); }); }); group('estimatedSizeMb', () { test('scales linearly with tile count', () { expect(estimatedSizeMb(0), 0); expect(estimatedSizeMb(1024, avgTileSizeKb: 1), closeTo(1.0, 1e-9)); }); }); }