410 lines
14 KiB
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')));
|
|
});
|
|
}
|