tasq/test/dashboard_metrics_provider_test.dart

410 lines
14 KiB
Dart

// ignore_for_file: deprecated_member_use
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tasq/models/it_service_request.dart';
import 'package:tasq/models/it_service_request_assignment.dart';
import 'package:tasq/models/profile.dart';
import 'package:tasq/models/ticket.dart';
import 'package:tasq/models/task.dart';
import 'package:tasq/models/task_assignment.dart';
import 'package:tasq/models/ticket_message.dart';
import 'package:tasq/models/duty_schedule.dart';
import 'package:tasq/models/attendance_log.dart';
import 'package:tasq/models/live_position.dart';
import 'package:tasq/models/leave_of_absence.dart';
import 'package:tasq/models/pass_slip.dart';
import 'package:tasq/models/team.dart';
import 'package:tasq/models/team_member.dart';
import 'package:tasq/screens/dashboard/dashboard_screen.dart';
import 'package:tasq/utils/app_time.dart';
// underlying providers needed by the dashboard metrics logic
import 'package:tasq/providers/it_service_request_provider.dart';
import 'package:tasq/providers/profile_provider.dart';
import 'package:tasq/providers/tickets_provider.dart';
import 'package:tasq/providers/tasks_provider.dart';
import 'package:tasq/providers/workforce_provider.dart'; // dutySchedulesProvider
import 'package:tasq/providers/attendance_provider.dart';
import 'package:tasq/providers/whereabouts_provider.dart';
import 'package:tasq/providers/leave_provider.dart';
import 'package:tasq/providers/pass_slip_provider.dart';
import 'package:tasq/providers/teams_provider.dart';
void main() {
group('dashboardMetricsProvider', () {
test('service request in progress causes status to become On task', () async {
AppTime.initialize();
final now = AppTime.now();
// create a single IT staff profile
final profile = Profile(
id: 'u1',
fullName: 'Staff One',
role: 'it_staff',
avatarUrl: null,
allowTracking: false,
);
final service = ItServiceRequest(
id: 'r1',
services: ['wifi'],
eventName: 'Event',
status: ItServiceRequestStatus.inProgress,
outsidePremiseAllowed: false,
createdAt: now,
updatedAt: now,
);
final assignment = ItServiceRequestAssignment(
id: 'a1',
requestId: 'r1',
userId: 'u1',
createdAt: now,
);
final container = ProviderContainer(
overrides: [
profilesProvider.overrideWith((ref) => Stream.value([profile])),
itServiceRequestsProvider.overrideWith(
(ref) => Stream.value([service]),
),
itServiceRequestAssignmentsProvider.overrideWith(
(ref) => Stream.value([assignment]),
),
// everything else can be empty streams so dashboard metrics doesn't crash
ticketsProvider.overrideWith((ref) => Stream.value(const <Ticket>[])),
tasksProvider.overrideWith((ref) => Stream.value(const <Task>[])),
taskAssignmentsProvider.overrideWith(
(ref) => Stream.value(const <TaskAssignment>[]),
),
ticketMessagesAllProvider.overrideWith(
(ref) => Stream.value(const <TicketMessage>[]),
),
dutySchedulesProvider.overrideWith(
(ref) => Stream.value(const <DutySchedule>[]),
),
attendanceLogsProvider.overrideWith(
(ref) => Stream.value(const <AttendanceLog>[]),
),
livePositionsProvider.overrideWith(
(ref) => Stream.value(const <LivePosition>[]),
),
leavesProvider.overrideWith(
(ref) => Stream.value(const <LeaveOfAbsence>[]),
),
passSlipsProvider.overrideWithProvider(
StreamProvider<List<PassSlip>>(
(ref) => Stream.value(const <PassSlip>[]),
),
),
teamsProvider.overrideWithProvider(
StreamProvider<List<Team>>((ref) => Stream.value(const <Team>[])),
),
teamMembersProvider.overrideWithProvider(
StreamProvider<List<TeamMember>>(
(ref) => Stream.value(const <TeamMember>[]),
),
),
],
);
// wait for the dashboard metrics provider to emit AsyncData
final completer = Completer<DashboardMetrics>();
container.listen<AsyncValue<DashboardMetrics>>(dashboardMetricsProvider, (
prev,
next,
) {
if (next is AsyncData<DashboardMetrics>) {
completer.complete(next.value);
}
}, fireImmediately: true);
final data = await completer.future;
expect(data.staffRows.length, 1);
expect(data.staffRows.first.status, 'On event');
});
test('service request not in progress does not affect status', () async {
AppTime.initialize();
final now = AppTime.now();
final profile = Profile(
id: 'u2',
fullName: 'Staff Two',
role: 'it_staff',
avatarUrl: null,
allowTracking: false,
);
final service = ItServiceRequest(
id: 'r2',
services: ['wifi'],
eventName: 'Event',
status: ItServiceRequestStatus.scheduled,
outsidePremiseAllowed: false,
createdAt: now,
updatedAt: now,
);
final assignment = ItServiceRequestAssignment(
id: 'a2',
requestId: 'r2',
userId: 'u2',
createdAt: now,
);
final container = ProviderContainer(
overrides: [
profilesProvider.overrideWith((ref) => Stream.value([profile])),
itServiceRequestsProvider.overrideWith(
(ref) => Stream.value([service]),
),
itServiceRequestAssignmentsProvider.overrideWith(
(ref) => Stream.value([assignment]),
),
ticketsProvider.overrideWith((ref) => Stream.value(const <Ticket>[])),
tasksProvider.overrideWith((ref) => Stream.value(const <Task>[])),
taskAssignmentsProvider.overrideWith(
(ref) => Stream.value(const <TaskAssignment>[]),
),
ticketMessagesAllProvider.overrideWith(
(ref) => Stream.value(const <TicketMessage>[]),
),
dutySchedulesProvider.overrideWith(
(ref) => Stream.value(const <DutySchedule>[]),
),
attendanceLogsProvider.overrideWith(
(ref) => Stream.value(const <AttendanceLog>[]),
),
livePositionsProvider.overrideWith(
(ref) => Stream.value(const <LivePosition>[]),
),
leavesProvider.overrideWith(
(ref) => Stream.value(const <LeaveOfAbsence>[]),
),
passSlipsProvider.overrideWithProvider(
StreamProvider<List<PassSlip>>(
(ref) => Stream.value(const <PassSlip>[]),
),
),
teamsProvider.overrideWithProvider(
StreamProvider<List<Team>>((ref) => Stream.value(const <Team>[])),
),
teamMembersProvider.overrideWithProvider(
StreamProvider<List<TeamMember>>(
(ref) => Stream.value(const <TeamMember>[]),
),
),
],
);
final completer = Completer<DashboardMetrics>();
container.listen<AsyncValue<DashboardMetrics>>(dashboardMetricsProvider, (
prev,
next,
) {
if (next is AsyncData<DashboardMetrics>) {
completer.complete(next.value);
}
}, fireImmediately: true);
final data = await completer.future;
expect(data.staffRows.length, 1);
// since user has no attendance or task data they should default to Off duty
expect(data.staffRows.first.status.toLowerCase(), contains('off'));
});
});
test(
'approved leave should force On leave status and not mark Off duty',
() async {
AppTime.initialize();
final now = AppTime.now();
final profile = Profile(
id: 'u3',
fullName: 'Staff Three',
role: 'it_staff',
avatarUrl: null,
allowTracking: false,
);
// create a schedule for today
final schedule = DutySchedule(
id: 's1',
userId: profile.id,
shiftType: 'am',
startTime: DateTime(now.year, now.month, now.day, 8),
endTime: DateTime(now.year, now.month, now.day, 16),
status: 'scheduled',
createdAt: now,
checkInAt: null,
checkInLocation: null,
relieverIds: [],
);
final leave = LeaveOfAbsence(
id: 'l1',
userId: profile.id,
leaveType: 'sick_leave',
justification: 'ill',
startTime: now.subtract(const Duration(hours: 1)),
endTime: now.add(const Duration(hours: 4)),
status: 'approved',
filedBy: profile.id,
createdAt: now,
);
final container = ProviderContainer(
overrides: [
profilesProvider.overrideWith((ref) => Stream.value([profile])),
dutySchedulesProvider.overrideWith((ref) => Stream.value([schedule])),
leavesProvider.overrideWith((ref) => Stream.value([leave])),
// stub the rest with empties
ticketsProvider.overrideWith((ref) => Stream.value(const <Ticket>[])),
tasksProvider.overrideWith((ref) => Stream.value(const <Task>[])),
taskAssignmentsProvider.overrideWith(
(ref) => Stream.value(const <TaskAssignment>[]),
),
ticketMessagesAllProvider.overrideWith(
(ref) => Stream.value(const <TicketMessage>[]),
),
attendanceLogsProvider.overrideWith(
(ref) => Stream.value(const <AttendanceLog>[]),
),
livePositionsProvider.overrideWith(
(ref) => Stream.value(const <LivePosition>[]),
),
passSlipsProvider.overrideWithProvider(
StreamProvider<List<PassSlip>>(
(ref) => Stream.value(const <PassSlip>[]),
),
),
teamsProvider.overrideWithProvider(
StreamProvider<List<Team>>((ref) => Stream.value(const <Team>[])),
),
teamMembersProvider.overrideWithProvider(
StreamProvider<List<TeamMember>>(
(ref) => Stream.value(const <TeamMember>[]),
),
),
itServiceRequestsProvider.overrideWith(
(ref) => Stream.value(const <ItServiceRequest>[]),
),
itServiceRequestAssignmentsProvider.overrideWith(
(ref) => Stream.value(const <ItServiceRequestAssignment>[]),
),
],
);
final completer = Completer<DashboardMetrics>();
container.listen<AsyncValue<DashboardMetrics>>(dashboardMetricsProvider, (
prev,
next,
) {
if (next is AsyncData<DashboardMetrics>) {
completer.complete(next.value);
}
}, fireImmediately: true);
final data = await completer.future;
expect(data.staffRows.length, 1);
expect(data.staffRows.first.status, equals('On leave'));
},
);
test('rejected leave should not affect schedule status', () async {
AppTime.initialize();
final now = AppTime.now();
final profile = Profile(
id: 'u4',
fullName: 'Staff Four',
role: 'it_staff',
avatarUrl: null,
allowTracking: false,
);
final schedule = DutySchedule(
id: 's2',
userId: profile.id,
shiftType: 'am',
startTime: DateTime(now.year, now.month, now.day, 8),
endTime: DateTime(now.year, now.month, now.day, 16),
status: 'scheduled',
createdAt: now,
checkInAt: null,
checkInLocation: null,
relieverIds: [],
);
final leave = LeaveOfAbsence(
id: 'l2',
userId: profile.id,
leaveType: 'vacation_leave',
justification: 'plans',
startTime: now.subtract(const Duration(hours: 1)),
endTime: now.add(const Duration(hours: 4)),
status: 'rejected',
filedBy: profile.id,
createdAt: now,
);
final container = ProviderContainer(
overrides: [
profilesProvider.overrideWith((ref) => Stream.value([profile])),
dutySchedulesProvider.overrideWith((ref) => Stream.value([schedule])),
leavesProvider.overrideWith((ref) => Stream.value([leave])),
ticketsProvider.overrideWith((ref) => Stream.value(const <Ticket>[])),
tasksProvider.overrideWith((ref) => Stream.value(const <Task>[])),
taskAssignmentsProvider.overrideWith(
(ref) => Stream.value(const <TaskAssignment>[]),
),
ticketMessagesAllProvider.overrideWith(
(ref) => Stream.value(const <TicketMessage>[]),
),
attendanceLogsProvider.overrideWith(
(ref) => Stream.value(const <AttendanceLog>[]),
),
livePositionsProvider.overrideWith(
(ref) => Stream.value(const <LivePosition>[]),
),
passSlipsProvider.overrideWithProvider(
StreamProvider<List<PassSlip>>(
(ref) => Stream.value(const <PassSlip>[]),
),
),
teamsProvider.overrideWithProvider(
StreamProvider<List<Team>>((ref) => Stream.value(const <Team>[])),
),
teamMembersProvider.overrideWithProvider(
StreamProvider<List<TeamMember>>(
(ref) => Stream.value(const <TeamMember>[]),
),
),
itServiceRequestsProvider.overrideWith(
(ref) => Stream.value(const <ItServiceRequest>[]),
),
itServiceRequestAssignmentsProvider.overrideWith(
(ref) => Stream.value(const <ItServiceRequestAssignment>[]),
),
],
);
final completer = Completer<DashboardMetrics>();
container.listen<AsyncValue<DashboardMetrics>>(dashboardMetricsProvider, (
prev,
next,
) {
if (next is AsyncData<DashboardMetrics>) {
completer.complete(next.value);
}
}, fireImmediately: true);
final data = await completer.future;
expect(data.staffRows.length, 1);
// since leave was rejected, status should not be 'On leave' nor 'Off duty'
expect(data.staffRows.first.status, isNot('On leave'));
expect(data.staffRows.first.status.toLowerCase(), isNot(contains('off')));
});
}