diff --git a/test/layout_test.dart b/test/layout_test.dart new file mode 100644 index 0000000..9c346c9 --- /dev/null +++ b/test/layout_test.dart @@ -0,0 +1,78 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tree_graph/flutter_tree_graph.dart'; + +class TestNode extends TreeNodeData { + final String _id; + final List _parentIds; + + TestNode(this._id, [this._parentIds = const []]); + + @override + String get id => _id; + + @override + List get parentIds => _parentIds; +} + +void main() { + group('SimpleLayout', () { + test('positions single root node at origin', () { + final root = TreeNode(TestNode('root')); + const layout = SimpleLayout(); + + layout.calculateLayout([root]); + + expect(root.x, 0.0); + expect(root.y, 0.0); + }); + + test('positions child below parent', () { + final root = TreeNode(TestNode('root')); + final child = TreeNode(TestNode('child')); + + root.children.add(child); + child.parents.add(root); + + const layout = SimpleLayout(); + layout.calculateLayout( + [root], + nodeWidth: 100, + nodeHeight: 50, + verticalSpacing: 50, + ); + + // Child should be at same X as parent (since it's the only child) + // and below parent by verticalSpacing + expect(child.x, 0.0); + expect( + child.y, + 50.0, + ); // 0 + verticalSpacing (assuming y starts at 0 for each level?) + // Actually SimpleLayout accumulates y + vSpacing + // root.y=0, child.y = 0 + 50 = 50. + }); + + test('positions siblings with spacing', () { + final root = TreeNode(TestNode('root')); + final child1 = TreeNode(TestNode('child1')); + final child2 = TreeNode(TestNode('child2')); + + root.children.addAll([child1, child2]); + + const layout = SimpleLayout(); + layout.calculateLayout([root], nodeWidth: 100, horizontalSpacing: 20); + + // Child 1 starts at parent's X (initially 0) + expect(child1.x, 0.0); + + // Child 2 starts at Child 1's X + nodeWidth + spacing + // 0 + 100 + 20 = 120 + expect(child2.x, 120.0); + + // Parent should be centered over children + // Center of children block: (0 + (120 + 100)) / 2 = 110 + // Parent center: 110 - (100 / 2) = 60 + expect(root.x, 60.0); + }); + }); +} diff --git a/test/models_test.dart b/test/models_test.dart new file mode 100644 index 0000000..0954374 --- /dev/null +++ b/test/models_test.dart @@ -0,0 +1,70 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tree_graph/flutter_tree_graph.dart'; + +class TestNode extends TreeNodeData { + final String _id; + final List _parentIds; + + TestNode(this._id, [this._parentIds = const []]); + + @override + String get id => _id; + + @override + List get parentIds => _parentIds; +} + +void main() { + group('TreeNodeData', () { + test('equality works correctly', () { + final node1 = TestNode('1'); + final node2 = TestNode('1'); + final node3 = TestNode('3'); + + expect(node1, equals(node2)); + expect(node1, isNot(equals(node3))); + }); + + test('hashCode is consistent', () { + final node1 = TestNode('1'); + final node2 = TestNode('1'); + + expect(node1.hashCode, equals(node2.hashCode)); + }); + }); + + group('TreeNode', () { + test('initialization sets default values', () { + final data = TestNode('1'); + final node = TreeNode(data); + + expect(node.data, equals(data)); + expect(node.parents, isEmpty); + expect(node.children, isEmpty); + expect(node.x, 0.0); + expect(node.y, 0.0); + }); + + test('structure properties work correctly', () { + final rootData = TestNode('root'); + final childData = TestNode('child', ['root']); + + final root = TreeNode(rootData); + final child = TreeNode(childData); + + // Manually link for unit testing structure properties + // Note: In real usage, TreeBuilder handles this + root.children.add(child); + child.parents.add(root); + + expect(root.isRoot, isTrue); + expect(root.isLeaf, isFalse); + expect(root.leftmostChild, equals(child)); + expect(root.rightmostChild, equals(child)); + + expect(child.isRoot, isFalse); + expect(child.isLeaf, isTrue); + expect(child.leftmostChild, isNull); + }); + }); +} diff --git a/test/partner_test.dart b/test/partner_test.dart new file mode 100644 index 0000000..51e34f5 --- /dev/null +++ b/test/partner_test.dart @@ -0,0 +1,70 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tree_graph/flutter_tree_graph.dart'; +import 'package:flutter_tree_graph/src/utils/tree_builder.dart'; + +class TestNode extends TreeNodeData { + final String _id; + final List _parentIds; + final String? _partnerId; + + TestNode(this._id, {List parentIds = const [], String? partnerId}) + : _parentIds = parentIds, + _partnerId = partnerId; + + @override + String get id => _id; + + @override + List get parentIds => _parentIds; + + @override + String? get partnerId => _partnerId; +} + +void main() { + group('TreeBuilder Partner Logic', () { + test('Root couple: Only one becomes root', () { + final data = [ + TestNode('A', partnerId: 'B'), + TestNode('B', partnerId: 'A'), + ]; + + final builder = TreeBuilder(); + final roots = builder.buildTree(data); + + expect(roots.length, 1); + final root = roots.first; + expect(root.data.id, anyOf('A', 'B')); + expect(root.partner, isNotNull); + expect(root.partner!.data.id, anyOf('A', 'B')); + expect(root.partner!.data.id, isNot(root.data.id)); + }); + + test('Descendant couple: Partner is not added to roots', () { + final data = [ + TestNode('Root'), + TestNode('Child', parentIds: ['Root'], partnerId: 'Spouse'), + TestNode( + 'Spouse', + partnerId: 'Child', + ), // No parents, but partner has parents + ]; + + final builder = TreeBuilder(); + final roots = builder.buildTree(data); + + expect(roots.length, 1); + final root = roots.first; + expect(root.data.id, 'Root'); + + expect(root.children, hasLength(1)); + final child = root.children.first; + expect(child.data.id, 'Child'); + expect(child.partner, isNotNull); + expect(child.partner!.data.id, 'Spouse'); + + // Verify 'Spouse' is NOT a root + // (If Spouse were a root, roots.length would be 2) + }); + }); +} diff --git a/test/walkers_test.dart b/test/walkers_test.dart new file mode 100644 index 0000000..ac1d5d7 --- /dev/null +++ b/test/walkers_test.dart @@ -0,0 +1,92 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tree_graph/flutter_tree_graph.dart'; +import 'package:flutter_tree_graph/src/layout/walkers_layout.dart'; + +class SimpleNode extends TreeNodeData { + final String _id; + SimpleNode(this._id); + + @override + String get id => _id; + + @override + List get parentIds => []; + + @override + // ignore: annotate_overrides + String? get partnerId => null; +} + +TreeNode node(String id) { + return TreeNode(SimpleNode(id)); +} + +void connect(TreeNode parent, List> children) { + for (var child in children) { + if (!parent.children.contains(child)) parent.children.add(child); + if (!child.parents.contains(parent)) child.parents.add(parent); + } +} + +void wirePartner(TreeNode p1, TreeNode p2) { + p1.partner = p2; + p2.partner = p1; +} + +void main() { + group('WalkersTreeLayout', () { + const layout = WalkersTreeLayout(); + const double w = 100; + const double h = 80; + const double hSpace = 50; + const double vSpace = 100; + + test('Single root node positioning', () { + final root = node('Root'); + layout.calculateLayout([root], nodeWidth: w, horizontalSpacing: hSpace); + + expect(root.x, 0); + expect(root.y, 0); + }); + + test('Parent with one child (centered)', () { + final child = node('Child'); + final root = node('Root'); + connect(root, [child]); + + layout.calculateLayout([root], nodeWidth: w, horizontalSpacing: hSpace); + + expect(child.x, 0); + expect(root.x, 0); + expect(child.y, vSpace); + }); + + test('Parent with two children (spaced)', () { + final c1 = node('C1'); + final c2 = node('C2'); + final root = node('Root'); + connect(root, [c1, c2]); + + layout.calculateLayout([root], nodeWidth: w, horizontalSpacing: hSpace); + + // c1 starts at 0 + expect(c1.x, 0); + // c2 starts at 100 + 50 = 150 + expect(c2.x, 150); + // Parent centered at (0 + 150) / 2 = 75 + expect(root.x, 75); + }); + + test('Partner handling (Couple width)', () { + final p1 = node('P1'); + final p2 = node('P2'); + wirePartner(p1, p2); + + final root = p1; + layout.calculateLayout([root], nodeWidth: w, horizontalSpacing: hSpace); + + expect(p1.x, 0); + expect(p2.x, 150); // straight to right + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..f950e45 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_tree_graph/flutter_tree_graph.dart'; +import 'package:flutter_tree_graph/src/widgets/tree_painter.dart'; + +class TestNode extends TreeNodeData { + final String _id; + final List _parentIds; + final String label; + + TestNode(this._id, this.label, [this._parentIds = const []]); + + @override + String get id => _id; + + @override + List get parentIds => _parentIds; +} + +void main() { + testWidgets('TreeView renders nodes correctly', (WidgetTester tester) async { + final data = [ + TestNode('1', 'Root'), + TestNode('2', 'Child', ['1']), + ]; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TreeView( + data: data, + nodeBuilder: (context, node) { + return Text(node.label); + }, + ), + ), + ), + ); + + // Verify nodes are rendered + expect(find.text('Root'), findsOneWidget); + expect(find.text('Child'), findsOneWidget); + + // Verify CustomPaint is present (for lines) + final customPaintFinder = find.byWidgetPredicate( + (widget) => widget is CustomPaint && widget.painter is TreePainter, + ); + expect(customPaintFinder, findsOneWidget); + }); + + testWidgets('TreeView handles empty data', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: TreeView(data: [], nodeBuilder: _buildNode), + ), + ), + ); + + expect(find.text('No data'), findsOneWidget); + }); +} + +Widget _buildNode(BuildContext context, TestNode node) { + return Text(node.label); +}