// Tests for the 双链 (bidirectional link) pure core (F7): [[link]] parsing + // forward/backlink indices + dangling-link detection. import 'package:flutter_test/flutter_test.dart'; import 'package:badnote/editor/link/link_graph.dart'; void main() { group('parseLinkTargets', () { test('extracts bracketed targets, trimmed', () { expect(parseLinkTargets('see [[Alpha]] and [[ Beta ]]'), ['Alpha', 'Beta']); }); test('de-duplicates, keeping first-occurrence order', () { expect(parseLinkTargets('[[A]] [[B]] [[A]]'), ['A', 'B']); }); test('ignores empty / whitespace-only targets', () { expect(parseLinkTargets('x [[]] y [[ ]] z [[Real]]'), ['Real']); }); test('no links → empty', () { expect(parseLinkTargets('plain text, no links'), isEmpty); }); }); group('LinkGraph', () { test('backlinksOf collects every node linking to a target', () { final g = LinkGraph.fromTexts({ 'n1': 'links to [[n2]] and [[n3]]', 'n2': 'links to [[n3]]', 'n3': 'a leaf', }); expect(g.linksFrom('n1'), {'n2', 'n3'}); expect(g.linksFrom('n3'), isEmpty); expect(g.backlinksOf('n3'), {'n1', 'n2'}); expect(g.backlinksOf('n2'), {'n1'}); expect(g.backlinksOf('n1'), isEmpty); }); test('self-links are ignored', () { final g = LinkGraph.fromTexts({'n1': 'I reference [[n1]] myself'}); expect(g.linksFrom('n1'), isEmpty); expect(g.backlinksOf('n1'), isEmpty); }); test('fromLinks builds the same indices from explicit targets', () { final g = LinkGraph.fromLinks({ 'a': ['b', 'c'], 'b': ['c'], 'c': [], }); expect(g.backlinksOf('c'), {'a', 'b'}); expect(g.linksFrom('a'), {'b', 'c'}); }); test('danglingTargets surfaces links to unknown nodes', () { final g = LinkGraph.fromLinks({ 'a': ['b', 'ghost'], 'b': [], }); expect(g.danglingTargets(), {'ghost'}); }); test('returned sets are unmodifiable', () { final g = LinkGraph.fromLinks({ 'a': ['b'], 'b': [], }); expect(() => g.linksFrom('a').add('x'), throwsUnsupportedError); expect(() => g.backlinksOf('b').add('x'), throwsUnsupportedError); }); test('unknown node ids yield empty (no crash)', () { final g = LinkGraph.fromLinks({'a': []}); expect(g.linksFrom('nope'), isEmpty); expect(g.backlinksOf('nope'), isEmpty); }); }); }