import { TestBed } from '@angular/core/testing'; import { of, throwError } from 'rxjs'; import { HttpErrorResponse } from '@angular/common/http'; import { Show, DanceGroup } from '../core/api-models'; import { SchoolApi } from '../core/school-api'; import { ScheduleEditor } from './schedule-editor'; const groups: DanceGroup[] = [1, 2, 3].map((id) => ({ id, category: `Group ${id}`, type: 'REGULAR', startAge: 0, endAge: 99, styles: [], dancerIds: [id], })); const show: Show = { id: 4, name: 'Show', preferredDayParts: 1, finalized: true, violations: [], appearances: {}, dayParts: [ { date: '2027-01-01', type: 'MORNING', danceGroupIds: [1, 2] }, { date: '2027-01-02', type: 'MORNING', danceGroupIds: [3] }, ], }; describe('ScheduleEditor', () => { const api = { saveAssignments: vi.fn(), finalize: vi.fn() }; beforeEach(() => { vi.resetAllMocks(); TestBed.configureTestingModule({ providers: [{ provide: SchoolApi, useValue: api }] }); }); function editor() { const fixture = TestBed.createComponent(ScheduleEditor); fixture.componentRef.setInput('show', show); fixture.componentRef.setInput('groups', groups); fixture.componentRef.setInput('schoolId', 7); fixture.componentInstance.ngOnInit(); return fixture.componentInstance; } it('edits order and dayparts without changing the saved input', () => { const component = editor(); component.reorder(0, 1, -1); component.move(0, 0, 1); component.add(0, 3); component.remove(1, 0); expect(component.assignments).toEqual([[1, 3], [2]]); expect(show.dayParts[0].danceGroupIds).toEqual([1, 2]); expect(component.dirty()).toBe(true); }); it('saves invalid drafts and displays server violations without finalizing', async () => { const component = editor(); component.remove(0, 0); const draft = { ...show, finalized: false, violations: ['Group 1 requires 1 dayparts.'] }; api.saveAssignments.mockReturnValue(of(draft)); await component.save(); expect(api.saveAssignments).toHaveBeenCalledWith(7, 4, [[2], [3]], undefined); expect(component.current()).toEqual(draft); expect(component.dirty()).toBe(false); expect(api.finalize).not.toHaveBeenCalled(); }); it('keeps unsaved edits when saving fails', async () => { const component = editor(); component.remove(0, 0); api.saveAssignments.mockReturnValue(throwError(() => new HttpErrorResponse({ status: 0 }))); await component.save(); expect(component.assignments).toEqual([[2], [3]]); expect(component.dirty()).toBe(true); expect(component.error()).toContain('niet bereikbaar'); expect(component.busy()).toBe(false); }); it('finalizes through the server and reports a stale validation failure', async () => { const component = editor(); api.finalize.mockReturnValue(of(show)); await component.finalize(); expect(component.current()?.finalized).toBe(true); api.finalize.mockReturnValue( throwError( () => new HttpErrorResponse({ status: 400, error: { message: 'Changing gap violated' } }), ), ); await component.finalize(); expect(component.error()).toContain('Changing gap violated'); }); });