import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:professional_repository/professional_repository.dart'; import 'package:prosappco/utils/slot_generator.dart'; import 'package:prosappco/utils/time_of_day_extension.dart'; import 'package:prosappco/utils/time_of_day_utils.dart'; void main() { _slotGeneratorTests(); group('TimeOfDayExtension.add', () { test('carries minutes into hours', () { expect(const TimeOfDay(hour: 8, minute: 45).add(minute: 45), const TimeOfDay(hour: 9, minute: 30)); }); test('does not wrap past midnight, so range loops still terminate', () { expect(const TimeOfDay(hour: 23, minute: 30).add(minute: 45).hour, 24); }); }); group('TimeOfDayUtils.genRanges', () { // Regression: a minute-based step used to leave the hour untouched // ("8:90"), so isBefore() never became false and this looped forever, // freezing the calendar screen. test('minute-based step terminates and lands on real times', () { final slots = TimeOfDayUtils.genRanges( const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0), stepMinutes: 45, ); expect(slots.length, 14); expect(slots.first, const TimeOfDay(hour: 8, minute: 0)); expect(slots[1], const TimeOfDay(hour: 8, minute: 45)); expect(slots[2], const TimeOfDay(hour: 9, minute: 30)); expect(slots.last, const TimeOfDay(hour: 17, minute: 45)); expect(slots.every((t) => t.minute < 60), isTrue); }); test('two-hour step keeps the historical behaviour', () { final slots = TimeOfDayUtils.genRanges( const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0), stepMinutes: 120, ); expect(slots.map((t) => t.hour).toList(), [8, 10, 12, 14, 16]); }); test('end before start yields no slots', () { final slots = TimeOfDayUtils.genRanges( const TimeOfDay(hour: 18, minute: 0), const TimeOfDay(hour: 8, minute: 0), stepMinutes: 45, ); expect(slots, isEmpty); }); }); } void _slotGeneratorTests() { group('SlotGenerator', () { final monday = DateTime(2026, 8, 24); // lunes ScheduleEntity continuous(TimeOfDay a, TimeOfDay b) => ScheduleEntity( enabled: true, continuousDay: true, range1Hour1: a, range1Hour2: null, range2Hour1: null, range2Hour2: b, ); test('a disabled day offers nothing', () { expect( SlotGenerator.forDay( schedule: continuous( const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0)) .copyWith(enabled: false), day: monday, slotDurationMinutes: 60, ), isEmpty, ); }); test('an inverted range offers nothing instead of looping forever', () { expect( SlotGenerator.forDay( schedule: continuous(const TimeOfDay(hour: 18, minute: 0), const TimeOfDay(hour: 8, minute: 0)), day: monday, slotDurationMinutes: 60, ), isEmpty, ); }); test('notBefore drops the slots already past', () { final slots = SlotGenerator.forDay( schedule: continuous(const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0)), day: monday, slotDurationMinutes: 60, notBefore: DateTime(2026, 8, 24, 15, 0), ); expect(slots.first, const TimeOfDay(hour: 15, minute: 0)); expect(slots.length, 3); // 15, 16, 17 }); test('endOf clamps instead of producing 25:00', () { expect(SlotGenerator.endOf(const TimeOfDay(hour: 23, minute: 0), 120), const TimeOfDay(hour: 23, minute: 59)); expect(SlotGenerator.endOf(const TimeOfDay(hour: 9, minute: 30), 45), const TimeOfDay(hour: 10, minute: 15)); }); }); }