import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:nuvolari/core/region/region_config.dart'; import 'package:nuvolari/features/places/comune.dart'; import 'package:nuvolari/features/places/comuni_repository.dart'; ComuneIndex loadBundled() => ComuneIndex.parse( File('assets/regions/piemonte_comuni.json').readAsStringSync(), ); void main() { group('foldForSearch', () { test('lower-cases and strips the accents Italian names use', () { expect(foldForSearch('Agliè'), 'aglie'); expect(foldForSearch('CIRIÈ'), 'cirie'); expect(foldForSearch('Mondovì'), 'mondovi'); expect(foldForSearch('Forlì'), 'forli'); }); test('leaves unaccented text alone apart from case', () { expect(foldForSearch('Torino'), 'torino'); expect(foldForSearch("Reggio nell'Emilia"), "reggio nell'emilia"); }); }); group('the bundled Piedmont list', () { late ComuneIndex index; late RegionConfig region; setUpAll(() { index = loadBundled(); region = RegionConfig.parse( File('assets/regions/piemonte.json').readAsStringSync(), ); }); test('holds every Piedmont municipality', () { // Piedmont had 1180 municipalities at 1 January 2025. A number that // drifts means the generator picked up the wrong region or year. expect(index.comuni, hasLength(1180)); }); test('names its source and licence', () { expect(index.source, contains('Istat')); expect(index.license, 'CC BY 4.0'); }); // A name decoded with the wrong codepage is the failure mode this data has // already produced once, and it is silent unless something checks. test('no name is mis-decoded', () { final suspicious = index.comuni .where( (comune) => comune.name.contains('Ã') || comune.name.contains('�'), ) .map((comune) => comune.name) .toList(); expect(suspicious, isEmpty); }); test('accented names survived the pipeline intact', () { final aglie = index.comuni.firstWhere((c) => c.istatCode == '001001'); expect(aglie.name, 'Agliè'); expect(aglie.province, 'TO'); }); test('every municipality falls inside the region bounds', () { final outside = index.comuni .where((comune) => !region.bounds.contains(comune.point)) .map((comune) => comune.label) .toList(); expect(outside, isEmpty); }); test('istat codes are unique', () { final codes = index.comuni.map((c) => c.istatCode).toSet(); expect(codes, hasLength(index.comuni.length)); }); test('provinces are the eight of Piedmont', () { final provinces = index.comuni.map((c) => c.province).toSet(); expect(provinces, {'TO', 'VC', 'NO', 'CN', 'AT', 'AL', 'BI', 'VB'}); }); test('the provincial capitals are where they should be', () { final torino = index.comuni.firstWhere((c) => c.name == 'Torino'); // A centroid, so a few kilometres out is expected; tens would mean the // projection maths is wrong. expect(torino.point.latitude, closeTo(45.07, 0.1)); expect(torino.point.longitude, closeTo(7.68, 0.1)); }); }); group('search', () { late ComuneIndex index; setUpAll(() => index = loadBundled()); test('finds a town by its exact name', () { final results = index.search('Torino'); expect(results.first.name, 'Torino'); }); // Someone typing "tor" wants Torino, not the first alphabetical name that // happens to contain those letters somewhere. test('ranks names that start with the query first', () { final results = index.search('tor'); expect(results.first.searchKey, startsWith('tor')); expect(results.map((c) => c.name), contains('Torino')); }); test('ignores accents in the query and in the data', () { expect(index.search('aglie').map((c) => c.name), contains('Agliè')); expect(index.search('Agliè').map((c) => c.name), contains('Agliè')); expect(index.search('mondovi').map((c) => c.name), contains('Mondovì')); }); test('is case-insensitive', () { expect(index.search('TORINO').first.name, 'Torino'); expect(index.search('torino').first.name, 'Torino'); }); test('matches in the middle of a name too', () { final results = index.search('mondov'); expect(results, isNotEmpty); }); test('an empty query returns nothing rather than everything', () { expect(index.search(''), isEmpty); expect(index.search(' '), isEmpty); }); test('caps the result list', () { // A single letter matches hundreds of names. expect( index.search('a').length, lessThanOrEqualTo(ComuneIndex.maxResults), ); }); test('an unmatched query returns nothing', () { expect(index.search('zzzzzzzz'), isEmpty); }); }); group('ComuneIndex.parse', () { test('rejects a document with no source', () { expect( () => ComuneIndex.parse( '{"places":[{"name":"X","province":"TO","istat":"1","lat":45,"lng":7}]}', ), throwsFormatException, ); }); test('rejects an empty list', () { expect( () => ComuneIndex.parse('{"source":"x","places":[]}'), throwsFormatException, ); }); }); group('ComuniRepository', () { test('derives the asset path from the region id', () { expect( ComuniRepository.assetPathFor('piemonte'), 'assets/regions/piemonte_comuni.json', ); }); }); }