Coverage for tests/test_diagram.py: 100%

70 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-05 04:36 +0000

1import plotly.graph_objects as go 

2import pytest 

3 

4from sankey_cashflow import AppSettings, RowLabels, Transactions, build_line_figure, build_sankey_figure, fetch_data 

5 

6 

7def _process(make_args, **overrides): 

8 args_kwargs = {'all_time': True, 'hover': 'Category'} 

9 args_kwargs.update(overrides) 

10 settings = AppSettings(make_args(**args_kwargs)) 

11 src_target, df = fetch_data(settings) 

12 labels = RowLabels(src_target) 

13 txn = Transactions(df, labels, settings) 

14 txn.process() 

15 return txn, labels, settings 

16 

17 

18@pytest.fixture 

19def processed_transactions(make_args): 

20 return _process(make_args) 

21 

22 

23class TestBuildSankeyFigure: 

24 

25 def test_returns_figure(self, processed_transactions): 

26 txn, labels, settings = processed_transactions 

27 fig = build_sankey_figure(txn, labels, settings) 

28 assert isinstance(fig, go.Figure) 

29 

30 def test_links_match_grouped_data_length(self, processed_transactions): 

31 txn, labels, settings = processed_transactions 

32 fig = build_sankey_figure(txn, labels, settings) 

33 assert len(fig.data[0].link.value) == len(txn.grouped_data) 

34 

35 def test_title_uses_transactions_title(self, processed_transactions): 

36 txn, labels, settings = processed_transactions 

37 fig = build_sankey_figure(txn, labels, settings) 

38 assert fig.layout.title.text == txn.title 

39 

40 def test_safe_to_call_more_than_once(self, processed_transactions): 

41 # Regression test: the original script mutated grouped_data in place (remapped Source/Target 

42 # to integer indices), so a second call would silently produce a broken figure. 

43 txn, labels, settings = processed_transactions 

44 fig1 = build_sankey_figure(txn, labels, settings) 

45 fig2 = build_sankey_figure(txn, labels, settings) 

46 assert list(fig1.data[0].link.value) == list(fig2.data[0].link.value) 

47 assert list(fig1.data[0].node.label) == list(fig2.data[0].node.label) 

48 

49 def test_hover_customdata_present_when_enabled(self, processed_transactions): 

50 txn, labels, settings = processed_transactions 

51 fig = build_sankey_figure(txn, labels, settings) 

52 assert fig.data[0].node.customdata is not None 

53 

54 def test_no_hover_customdata_when_disabled(self, make_args): 

55 # AppSettings only disables hover for the string 'none' (or 'no'/'false') - omitting the 

56 # flag entirely defaults to hover='Category' (enabled). 

57 txn, labels, settings = _process(make_args, hover='none') 

58 assert settings.hover is None 

59 fig = build_sankey_figure(txn, labels, settings) 

60 assert fig.data[0].node.customdata is None 

61 

62 

63class TestBuildLineFigure: 

64 

65 def test_returns_figure_with_traces(self, processed_transactions): 

66 txn, _, settings = processed_transactions 

67 settings.chart_resolution = 'week' 

68 fig = build_line_figure(txn, settings) 

69 assert isinstance(fig, go.Figure) 

70 assert len(fig.data) > 0 

71 

72 def test_excludes_income_and_uncategorized(self, processed_transactions): 

73 txn, _, settings = processed_transactions 

74 settings.chart_resolution = 'month' 

75 fig = build_line_figure(txn, settings) 

76 trace_names = [t.name for t in fig.data] 

77 assert 'Income' not in trace_names 

78 assert 'Uncategorized' not in trace_names 

79 

80 def test_excludes_x_prefixed_classifications(self, processed_transactions): 

81 txn, _, settings = processed_transactions 

82 settings.chart_resolution = 'month' 

83 fig = build_line_figure(txn, settings) 

84 trace_names = [t.name for t in fig.data] 

85 assert any(name.startswith('x') for name in txn.processed_data['Classification'].unique()) 

86 assert not any(name.startswith('x') for name in trace_names) 

87 

88 def test_does_not_mutate_processed_data(self, processed_transactions): 

89 txn, _, settings = processed_transactions 

90 settings.chart_resolution = 'month' 

91 original_columns = list(txn.processed_data.columns) 

92 build_line_figure(txn, settings) 

93 assert list(txn.processed_data.columns) == original_columns