Files
krow-worker-app/test/shift_logic_test.dart
2026-09-17 22:26:11 +05:30

129 lines
3.8 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:krow_worker_app/core/formatters.dart';
import 'package:krow_worker_app/models/shift.dart';
import 'package:krow_worker_app/models/timecard.dart';
Shift buildShift({
ShiftStatus status = ShiftStatus.booked,
DateTime? clockIn,
DateTime? clockOut,
int breakMinutes = 30,
}) {
final start = DateTime(2026, 8, 10, 17, 50);
return Shift(
id: 's1',
role: 'Barista',
business: 'Zoox',
venue: 'Zoox - San Jose Hub',
address: '848 E Gish Rd',
start: start,
end: start.add(const Duration(hours: 6)),
rate: 21.00,
status: status,
breakMinutes: breakMinutes,
clockIn: clockIn,
clockOut: clockOut,
);
}
void main() {
group('Shift pay', () {
test('scheduled pay deducts the unpaid break', () {
final shift = buildShift();
expect(shift.scheduledDuration, const Duration(hours: 5, minutes: 30));
expect(shift.scheduledPay, closeTo(115.50, 0.001));
});
test('worked hours replace the schedule once clocked out', () {
final shift = buildShift(
status: ShiftStatus.completed,
clockIn: DateTime(2026, 8, 10, 18, 1),
clockOut: DateTime(2026, 8, 10, 23, 50),
);
// 5h49m on the clock, less the 30 minute break.
expect(shift.workedDuration, const Duration(hours: 5, minutes: 19));
expect(shift.pay, closeTo(111.65, 0.01));
});
test('a clocked window shorter than the break never goes negative', () {
final shift = buildShift(
status: ShiftStatus.completed,
clockIn: DateTime(2026, 8, 10, 18, 0),
clockOut: DateTime(2026, 8, 10, 18, 5),
);
expect(shift.workedDuration, Duration.zero);
expect(shift.pay, 0);
});
test('cancelled and no-show shifts generate no pay', () {
expect(buildShift(status: ShiftStatus.cancelled).pay, 0);
expect(buildShift(status: ShiftStatus.noShow).pay, 0);
});
});
group('Timecard', () {
Timecard card({required DateTime clockIn}) {
final start = DateTime(2026, 8, 10, 17, 50);
return Timecard(
id: 'tc1',
shiftId: 's1',
role: 'Barista',
business: 'Zoox',
scheduledStart: start,
scheduledEnd: start.add(const Duration(hours: 6)),
clockIn: clockIn,
clockOut: DateTime(2026, 8, 10, 23, 50),
breakMinutes: 30,
rate: 21.00,
status: TimecardStatus.needsReview,
submittedAt: DateTime(2026, 8, 11, 0, 2),
);
}
test('variance is positive when you clock in late', () {
expect(card(clockIn: DateTime(2026, 8, 10, 18, 1)).varianceMinutes, 11);
});
test('variance is negative when you clock in early', () {
expect(card(clockIn: DateTime(2026, 8, 10, 17, 45)).varianceMinutes, -5);
});
test('amount follows the clocked hours', () {
expect(
card(clockIn: DateTime(2026, 8, 10, 18, 1)).amount,
closeTo(111.65, 0.01),
);
});
});
group('Formatters', () {
test('duration reads as hours and minutes', () {
expect(Fmt.duration(const Duration(hours: 5, minutes: 19)), '5h 19m');
expect(Fmt.duration(const Duration(minutes: 45)), '45m');
});
test('stopwatch pads every field', () {
expect(
Fmt.stopwatch(const Duration(hours: 1, minutes: 2, seconds: 3)),
'01:02:03',
);
});
test('countdown switches unit as the shift approaches', () {
final now = DateTime(2026, 8, 10, 12, 0);
expect(
Fmt.untilStart(now.add(const Duration(minutes: 41)), now: now),
'In 41 min',
);
expect(
Fmt.untilStart(now.add(const Duration(hours: 3)), now: now),
'In 3 hr',
);
expect(
Fmt.untilStart(now.subtract(const Duration(minutes: 5)), now: now),
'Started',
);
});
});
}