Files
court-pilot/src/app/tab1/tab1.page.spec.ts
T

246 lines
9.5 KiB
TypeScript

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { signal } from '@angular/core';
import { AlertController } from '@ionic/angular';
import { CourtServiceJudge, CourtUmpire, Settings, Umpire } from 'db';
import { Tab1Page } from './tab1.page';
import { SettingsService } from '../services/settings-service';
import { UmpireService } from '../services/umpire.service';
import { WaitingUmpiresService } from '../services/waiting-umpires.service';
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
import { CourtUmpireService } from '../services/court.umpire.service';
import { CourtServiceJudgeService } from '../services/court.service.judge.service';
// Keep page tests independent of IndexedDB and test the public court actions.
describe('Tab1Page court rotation', () => {
let component: Tab1Page;
let fixture: ComponentFixture<Tab1Page>;
const official = (id: number): Umpire => ({
id,
firstName: `Official ${id}`,
lastName: 'Smith',
country: 'GB',
gender: 'M'
});
const settings = signal<Settings>({
id: 1,
numberOfCourts: 3,
withServiceJudge: true,
showAlert: false
});
const umpires = signal<Umpire[]>([]);
const courtUmpires = signal<CourtUmpire[]>([]);
const courtJudges = signal<CourtServiceJudge[]>([]);
let waitingUmpire: jasmine.SpyObj<WaitingUmpiresService>;
let waitingJudge: jasmine.SpyObj<WaitingServiceJudgesService>;
let umpireCourt: jasmine.SpyObj<CourtUmpireService>;
let judgeCourt: jasmine.SpyObj<CourtServiceJudgeService>;
let alerts: jasmine.SpyObj<AlertController>;
beforeEach(() => {
settings.set({
id: 1,
numberOfCourts: 3,
withServiceJudge: true,
showAlert: false
});
umpires.set([
official(1),
official(2),
official(3),
official(4),
official(5)
]);
courtUmpires.set([]);
courtJudges.set([]);
waitingUmpire = jasmine.createSpyObj(
'WaitingUmpiresService',
['getCurrentUmpire', 'add', 'removeByUmpireId'],
{ waitingUmpires: signal([{ id: 1, umpireId: 1, order: 1 }]) }
);
waitingJudge = jasmine.createSpyObj(
'WaitingServiceJudgesService',
['getCurrentUmpire', 'add', 'removeByUmpireId'],
{ waitingServiceJudges: signal([{ id: 1, serviceJudgeId: 2, order: 1 }]) }
);
umpireCourt = jasmine.createSpyObj(
'CourtUmpireService',
['save', 'removeByUmpireId'],
{ umpires: courtUmpires }
);
judgeCourt = jasmine.createSpyObj(
'CourtServiceJudgeService',
['save', 'removeByUmpireId'],
{ umpires: courtJudges }
);
alerts = jasmine.createSpyObj('AlertController', ['create']);
waitingUmpire.getCurrentUmpire.and.resolveTo(official(1));
waitingJudge.getCurrentUmpire.and.resolveTo(official(2));
waitingUmpire.add.and.resolveTo(1);
waitingJudge.add.and.resolveTo(1);
umpireCourt.save.and.resolveTo(1);
judgeCourt.save.and.resolveTo(1);
waitingUmpire.removeByUmpireId.and.resolveTo();
waitingJudge.removeByUmpireId.and.resolveTo();
umpireCourt.removeByUmpireId.and.resolveTo();
judgeCourt.removeByUmpireId.and.resolveTo();
TestBed.configureTestingModule({
providers: [
{ provide: SettingsService, useValue: { settings } },
{ provide: UmpireService, useValue: { umpires } },
{ provide: WaitingUmpiresService, useValue: waitingUmpire },
{ provide: WaitingServiceJudgesService, useValue: waitingJudge },
{ provide: CourtUmpireService, useValue: umpireCourt },
{ provide: CourtServiceJudgeService, useValue: judgeCourt },
{ provide: AlertController, useValue: alerts }
]
});
fixture = TestBed.createComponent(Tab1Page);
component = fixture.componentInstance;
});
it('waits for confirmation before assigning officials', async () => {
settings.update((value) => ({ ...value, showAlert: true }));
const present = jasmine.createSpy('present').and.resolveTo();
alerts.create.and.resolveTo({ present } as unknown as HTMLIonAlertElement);
await component.showAddConfirmation(2);
expect(present).toHaveBeenCalledTimes(1);
expect(umpireCourt.save).not.toHaveBeenCalled();
expect(judgeCourt.save).not.toHaveBeenCalled();
const options = alerts.create.calls.mostRecent().args[0];
const confirm = options?.buttons?.find(
(button) => typeof button !== 'string' && button.role === 'confirm'
);
expect(confirm).toBeDefined();
if (typeof confirm === 'object' && typeof confirm.handler === 'function') {
confirm.handler(undefined);
}
await fixture.whenStable();
expect(umpireCourt.save).toHaveBeenCalledOnceWith({
courtNo: 2,
umpireId: 1
});
expect(judgeCourt.save).toHaveBeenCalledOnceWith({
courtNo: 2,
umpireId: 2
});
});
it('returns a lone umpire to the umpire queue after confirming rotation', async () => {
settings.update((value) => ({
...value,
showAlert: true,
withServiceJudge: false
}));
courtUmpires.set([{ id: 1, umpireId: 3, courtNo: 2 }]);
const present = jasmine.createSpy('present').and.resolveTo();
alerts.create.and.resolveTo({ present } as unknown as HTMLIonAlertElement);
await component.showRemoveConfirmation(2);
expect(umpireCourt.removeByUmpireId).not.toHaveBeenCalled();
const options = alerts.create.calls.mostRecent().args[0];
const confirm = options?.buttons?.find(
(button) => typeof button !== 'string' && button.role === 'confirm'
);
expect(confirm).toBeDefined();
if (typeof confirm === 'object' && typeof confirm.handler === 'function') {
confirm.handler(undefined);
}
await fixture.whenStable();
expect(umpireCourt.removeByUmpireId).toHaveBeenCalledOnceWith(3);
expect(waitingUmpire.add).toHaveBeenCalledOnceWith(3);
expect(waitingJudge.add).not.toHaveBeenCalled();
expect(judgeCourt.removeByUmpireId).not.toHaveBeenCalled();
});
it('assigns both queue heads and removes them from their queues', async () => {
await component.showAddConfirmation(3);
await fixture.whenStable();
expect(umpireCourt.save).toHaveBeenCalledOnceWith({
courtNo: 3,
umpireId: 1
});
expect(judgeCourt.save).toHaveBeenCalledOnceWith({
courtNo: 3,
umpireId: 2
});
expect(waitingUmpire.removeByUmpireId).toHaveBeenCalledOnceWith(1);
expect(waitingJudge.removeByUmpireId).toHaveBeenCalledOnceWith(2);
expect(alerts.create).not.toHaveBeenCalled();
});
it('does not assign either role when the required service judge is missing', async () => {
waitingJudge.getCurrentUmpire.and.resolveTo(undefined);
await component.showAddConfirmation(1);
expect(umpireCourt.save).not.toHaveBeenCalled();
expect(judgeCourt.save).not.toHaveBeenCalled();
expect(waitingUmpire.removeByUmpireId).not.toHaveBeenCalled();
});
it('does not assign when the umpire queue is empty', async () => {
waitingUmpire.getCurrentUmpire.and.resolveTo(undefined);
await component.showAddConfirmation(1);
expect(umpireCourt.save).not.toHaveBeenCalled();
expect(judgeCourt.save).not.toHaveBeenCalled();
});
it('assigns only an umpire when service judges are disabled', async () => {
settings.update((value) => ({ ...value, withServiceJudge: false }));
await component.showAddConfirmation(2);
await fixture.whenStable();
expect(umpireCourt.save).toHaveBeenCalledOnceWith({
courtNo: 2,
umpireId: 1
});
expect(waitingUmpire.removeByUmpireId).toHaveBeenCalledOnceWith(1);
expect(judgeCourt.save).not.toHaveBeenCalled();
expect(waitingJudge.removeByUmpireId).not.toHaveBeenCalled();
});
it('swaps outgoing officials into the opposite role queues', async () => {
courtUmpires.set([{ id: 1, umpireId: 3, courtNo: 2 }]);
courtJudges.set([{ id: 1, umpireId: 4, courtNo: 2 }]);
await component.showRemoveConfirmation(2);
await fixture.whenStable();
expect(umpireCourt.removeByUmpireId).toHaveBeenCalledOnceWith(3);
expect(judgeCourt.removeByUmpireId).toHaveBeenCalledOnceWith(4);
expect(waitingUmpire.add).toHaveBeenCalledOnceWith(4);
expect(waitingJudge.add).toHaveBeenCalledOnceWith(3);
});
it('leaves an incomplete court unchanged when service judges are required', async () => {
courtUmpires.set([{ id: 1, umpireId: 3, courtNo: 2 }]);
await component.showRemoveConfirmation(2);
expect(umpireCourt.removeByUmpireId).not.toHaveBeenCalled();
expect(waitingUmpire.add).not.toHaveBeenCalled();
});
it('derives rest from all four duty states and reacts to assignment changes', () => {
courtUmpires.set([{ id: 1, umpireId: 3, courtNo: 2 }]);
courtJudges.set([{ id: 1, umpireId: 4, courtNo: 2 }]);
expect(component.onRest().map((item) => item.id)).toEqual([5]);
courtJudges.set([]);
expect(component.onRest().map((item) => item.id)).toEqual([4, 5]);
});
it('ignores empty and stale court assignments in its court lookup', () => {
courtUmpires.set([
{ id: 1, umpireId: null, courtNo: 1 },
{ id: 2, umpireId: 999, courtNo: 2 },
{ id: 3, umpireId: 3, courtNo: 3 }
]);
expect([...component.umpireByCourt().entries()]).toEqual([
[3, official(3)]
]);
});
it('updates court numbers and service judge availability from settings', () => {
expect(component.courtIndexes()).toEqual([1, 2, 3]);
settings.update((value) => ({
...value,
numberOfCourts: 1,
withServiceJudge: false
}));
expect(component.courtIndexes()).toEqual([1]);
expect(component.serviceJudgeEnabled()).toBeFalse();
});
});