import 'package:flutter_test/flutter_test.dart'; import 'package:please_pay_me/data/models/budget.dart'; import 'package:please_pay_me/data/models/expense.dart'; import 'package:please_pay_me/data/models/job.dart'; import 'package:please_pay_me/data/models/json.dart'; void main() { group('json helpers', () { test('coerce numbers coming as int, double or string', () { expect(asDouble(10), 10.0); expect(asDouble(10.5), 10.5); expect(asDouble('10,5'), 10.5); expect(asDouble(null, fallback: -1), -1); expect(asInt('42'), 42); }); test('asDate keeps the calendar day and drops time', () { expect(asDate('2026-09-19'), DateTime(2026, 9, 19)); expect(asDate('2026-09-19T23:45:00Z'), DateTime(2026, 9, 19)); }); test('formatIsoDate pads month and day', () { expect(formatIsoDate(DateTime(2026, 1, 5)), '2026-01-05'); }); }); group('BudgetStatus', () { BudgetStatus parse(Map overrides) { return BudgetStatus.fromJson({ 'budget': { 'id': 1, 'user_id': 7, 'name': 'До аванса', 'total_amount': 42000, 'start_date': '2026-09-13', 'end_date': '2026-09-27', 'currency': 'RUB', 'is_active': true, }, 'today': '2026-09-19', 'days_left': 9, 'total_spent': 21000, 'remaining': 21000, 'daily_limit': 2333.33, 'spent_today': 1200, 'remaining_today': 1133.33, 'is_over_daily': false, 'is_over_budget': false, 'is_expired': false, 'selected': true, ...overrides, }); } test('parses nested budget', () { final status = parse({}); expect(status.budget.name, 'До аванса'); expect(status.budget.endDate, DateTime(2026, 9, 27)); expect(status.selected, isTrue); }); test('spentProgress is a clamped share of the total', () { expect(parse({}).spentProgress, closeTo(0.5, 0.001)); expect(parse({'total_spent': 50000}).spentProgress, 1.0); }); test('dailyProgress falls back to full bar when the limit is zero', () { expect(parse({'daily_limit': 0, 'spent_today': 500}).dailyProgress, 1.0); expect(parse({'daily_limit': 0, 'spent_today': 0}).dailyProgress, 0.0); }); }); group('ExpensesPage', () { test('reports pagination state', () { final page = ExpensesPage.fromJson({ 'page': 1, 'total_pages': 3, 'total_count': 55, 'page_size': 20, 'total_sum': 12345.5, 'budget_id': 4, 'items': [ {'id': 1, 'budget_id': 4, 'amount': 250, 'note': 'Кофе', 'spent_at': '2026-09-19'}, {'id': 2, 'budget_id': 4, 'amount': 100.5, 'note': null, 'spent_at': '2026-09-18'}, ], }); expect(page.hasMore, isTrue); expect(page.items, hasLength(2)); expect(page.items.last.note, isNull); expect(page.totalSum, 12345.5); }); }); group('Job', () { test('parses pay days and the weekend policy', () { final job = Job.fromJson({ 'id': 1, 'user_id': 7, 'name': 'Основная', 'salary_amount': 180000, 'currency': 'RUB', 'pay_days': [5, 20], 'first_pay_percent': 40, 'weekend_policy': 'after_weekend', 'is_active': true, 'next_pays': [ {'date': '2026-09-20', 'scheduled_day': 20, 'percent': 60, 'amount': 108000}, ], }); expect(job.payDays, [5, 20]); expect(job.weekendPolicy, WeekendPolicy.afterWeekend); expect(job.nextPay?.amount, 108000); }); test('unknown weekend policy degrades to the default', () { expect(WeekendPolicy.fromWire('nonsense'), WeekendPolicy.beforeWeekend); }); }); }