S18 Added OR Tools rules for scheduling show
All checks were successful
Build, test and publish / verify (push) Successful in 6m12s

This commit is contained in:
2026-11-03 11:03:47 +01:00
parent c965f9b41d
commit a7f7730ea0
48 changed files with 2262 additions and 256 deletions

View File

@@ -70,6 +70,9 @@ describe('EntityEditor', () => {
const show: Show = {
id: 3,
name: 'Summer',
preferredDayParts: 2,
finalized: false,
violations: [],
dayParts: [
{ date: '2027-06-01', type: 'AFTERNOON', danceGroupIds: [4] },
{ date: '2027-06-02', type: 'AFTERNOON', danceGroupIds: [4] },
@@ -77,7 +80,7 @@ describe('EntityEditor', () => {
appearances: { REGULAR: 2, SELECTION: 2, PRE_SCHOOLER: 1, SHOW_CASE: 2, PRE_EDUCATION: 2 },
};
it('initializes defaults for every type and rejects duplicate dated dayparts and fractional counts', () => {
it('defaults x to two and rejects duplicate dated dayparts and fractional x', () => {
const component = editor({ kind: 'show' });
component.name = 'Show';
expect(component.validDetails()).toBe(true);
@@ -87,31 +90,25 @@ describe('EntityEditor', () => {
component.dayParts[1].date = '2027-06-02';
component.addPart();
component.dayParts[2].date = '2027-06-03';
expect(component.appearances).toEqual({
REGULAR: 2,
SELECTION: 2,
PRE_SCHOOLER: 1,
SHOW_CASE: 3,
PRE_EDUCATION: 3,
});
expect(component.preferredDayParts).toBe(2);
expect(component.validDetails()).toBe(true);
component.dayParts[2].date = '2027-06-02';
expect(component.validDetails()).toBe(false);
component.dayParts[2].date = '2027-06-03';
component.appearances.PRE_SCHOOLER = 1.5;
component.preferredDayParts = 1.5;
expect(component.validDetails()).toBe(false);
});
it('preserves saved counts and requires explicit correction when reducing to one daypart', () => {
it('retains x when dayparts are removed so it can be explicitly corrected', () => {
const component = editor({ kind: 'show', value: show });
component.addPart();
component.dayParts[2].date = '2027-06-03';
expect(component.appearances.SHOW_CASE).toBe(2);
expect(component.preferredDayParts).toBe(2);
component.removePart(2);
component.removePart(1);
expect(component.appearances.REGULAR).toBe(2);
expect(component.validDetails()).toBe(false);
component.setSingleCounts();
expect(component.preferredDayParts).toBe(2);
expect(component.validDetails()).toBe(true);
component.preferredDayParts = 1;
expect(component.validDetails()).toBe(true);
});
@@ -120,11 +117,11 @@ describe('EntityEditor', () => {
const confirm = vi.spyOn(window, 'confirm').mockReturnValue(false);
try {
const component = editor({ kind: 'show', value: show });
component.appearances.REGULAR = 1;
component.preferredDayParts = 1;
await component.save();
expect(confirm).toHaveBeenCalledWith(expect.stringContaining('Summer'));
expect(api.saveShow).not.toHaveBeenCalled();
component.appearances.REGULAR = 2;
component.preferredDayParts = 2;
confirm.mockClear();
component.name = 'Renamed';
await component.save();
@@ -147,7 +144,7 @@ describe('EntityEditor', () => {
}
});
it('displays one disabled appearance field for every type with one daypart', async () => {
it('displays one editable x field with a default of one for a one-daypart show', async () => {
// jsdom does not implement native dialog opening.
const descriptor = Object.getOwnPropertyDescriptor(HTMLDialogElement.prototype, 'showModal');
Object.defineProperty(HTMLDialogElement.prototype, 'showModal', {
@@ -163,8 +160,8 @@ describe('EntityEditor', () => {
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
const inputs = Array.from(element.querySelectorAll<HTMLInputElement>('input[type="number"]'));
expect(inputs).toHaveLength(5);
expect(inputs.every((input) => input.disabled && input.value === '1')).toBe(true);
expect(inputs).toHaveLength(1);
expect(inputs.every((input) => !input.disabled && input.value === '1')).toBe(true);
} finally {
if (descriptor) Object.defineProperty(HTMLDialogElement.prototype, 'showModal', descriptor);
else Reflect.deleteProperty(HTMLDialogElement.prototype, 'showModal');