// Tests for search ranking + aggregation (F8). import 'package:flutter_test/flutter_test.dart'; import 'package:badnote/editor/search/search_ranking.dart'; void main() { group('scoreText', () { test('0 for empty query or no match', () { expect(scoreText('hello world', ''), 0); expect(scoreText('hello world', 'zzz'), 0); expect(scoreText('', 'x'), 0); }); test('more occurrences score higher', () { final one = scoreText('cat dog', 'cat'); final three = scoreText('cat cat cat', 'cat'); expect(three, greaterThan(one)); }); test('earlier first match breaks ties (same count)', () { final early = scoreText('needle then padding padding', 'needle'); final late = scoreText('padding padding then needle', 'needle'); expect(early, greaterThan(late)); }); test('a match at position 0 gives earliness 1 (score = count + 1)', () { expect(scoreText('cat', 'cat'), closeTo(2.0, 1e-9)); // 1 occ + 1.0 }); }); group('rankHits', () { test('drops non-matches and orders best-first', () { final hits = rankHits({ 'p1': 'one mention of fox', 'p2': 'fox fox fox everywhere', // 3 occ → highest 'p3': 'nothing relevant here', }, 'fox'); expect(hits.map((h) => h.ref), ['p2', 'p1']); expect(hits.first.score, greaterThan(hits.last.score)); }); test('each hit carries a display snippet of the original text', () { final hits = rankHits({'p1': 'The quick brown fox jumps'}, 'fox'); expect(hits, hasLength(1)); expect(hits.first.snippet.match, 'fox'); expect(hits.first.ref, 'p1'); }); test('equal scores keep input order (explicit tiebreak)', () { final hits = rankHits({ 'a': 'match', 'b': 'match', }, 'match'); expect(hits.map((h) => h.ref), ['a', 'b']); expect(hits[0].score, hits[1].score); }); test('empty query → no hits', () { expect(rankHits({'a': 'anything'}, ''), isEmpty); }); }); }