import { TestBed } from '@angular/core/testing'; import { RuleService, RuleType, IfThenRule, TimeWindowRule, OptimizationRule, } from './rule-service'; import { SelectableDayType } from './calendar-service'; import { OPTIMIZATION_PRESETS } from './planning-objectives'; import { vi } from '../../testing/vitest-compat'; describe('RuleService', () => { let service: RuleService; beforeEach(() => { // Clear localStorage before each test to ensure a clean state localStorage.clear(); TestBed.configureTestingModule({}); service = TestBed.inject(RuleService); }); it('should be created', () => { expect(service).toBeTruthy(); }); it('should initialize with default rules', () => { const rules = service.rules; expect(rules.length).toBe(8); expect(rules[0].id).toBe('default-unplaced-at-risk'); expect(rules[1].id).toBe('default-samedi-malin'); expect(rules[2].id).toBe('default-christmas'); }); it('should parse dates correctly for TimeWindowRules', () => { const rules = service.rules; const christmasRule = rules.find((r) => r.type === RuleType.TIME_WINDOW) as TimeWindowRule; expect(christmasRule).toBeDefined(); expect(christmasRule.startDate instanceof Date).toBe(true); expect(christmasRule.endDate instanceof Date).toBe(true); }); it('migrates legacy force values without changing rule order or dates', () => { TestBed.resetTestingModule(); localStorage.setItem( 'vacationRules', JSON.stringify([ { id: 'legacy-first', name: 'First', type: RuleType.TIME_WINDOW, enabled: true, startDate: '2024-02-01', endDate: '2024-02-05', placeDays: 1, placeType: SelectableDayType.CP, }, { id: 'legacy-second', name: 'Second', type: RuleType.TIME_WINDOW, enabled: true, force: 'obligation', startDate: '2024-03-01', endDate: '2024-03-02', placeDays: 1, placeType: SelectableDayType.RTT, }, ]), ); TestBed.configureTestingModule({}); service = TestBed.inject(RuleService); expect(service.rules.slice(1, 3).map((rule) => rule.id)).toEqual([ 'legacy-first', 'legacy-second', ]); expect(service.rules[1].force).toBe('preference'); const first = service.rules[1] as TimeWindowRule; expect(first.startDate.toISOString().slice(0, 10)).toBe('2024-02-01'); }); it('should add a new rule', () => { const newRule: IfThenRule = { id: 'test-rule', name: 'Test Rule', type: RuleType.IF_THEN, enabled: true, dayOfWeek: 1, // Monday placeDays: 1, placeType: SelectableDayType.RTT, placement: 'after', rewardDays: 0, rewardType: SelectableDayType.OTHER, }; service.addRule(newRule); const rules = service.rules; expect(rules.length).toBe(9); expect(rules.find((r) => r.id === 'test-rule')).toBeDefined(); }); it('should update an existing rule', () => { const rules = service.rules; const targetRule = { ...rules.find((rule) => rule.type === RuleType.IF_THEN)! }; targetRule.name = 'Updated Name'; service.updateRule(targetRule); const updatedRules = service.rules; const updated = updatedRules.find((r) => r.id === targetRule.id); expect(updated?.name).toBe('Updated Name'); }); it('should delete a rule', () => { const rules = service.rules; const idToDelete = rules[0].id; service.deleteRule(idToDelete); const updatedRules = service.rules; expect(updatedRules.length).toBe(7); expect(updatedRules.find((r) => r.id === idToDelete)).toBeUndefined(); }); it('should toggle rule enabled state', () => { const targetId = service.rules[0].id; const initialState = service.rules[0].enabled; service.toggleRule(targetId); expect(service.rules.find((r) => r.id === targetId)?.enabled).toBe(!initialState); }); it('should reorder rules correctly', () => { const rulesBefore = service.rules; const firstRuleId = rulesBefore[0].id; const secondRuleId = rulesBefore[1].id; // Move first rule to second position service.reorderRules(0, 1); const rulesAfter = service.rules; expect(rulesAfter[0].id).toBe(secondRuleId); expect(rulesAfter[1].id).toBe(firstRuleId); }); it('restores the full optimization identity, parameters, enabled state and position', () => { vi.useFakeTimers(); try { TestBed.tick(); const optimization: OptimizationRule = { id: 'personal-loss', name: 'My loss priority', type: RuleType.OPTIMIZATION, enabled: true, force: 'preference', configuration: structuredClone(OPTIMIZATION_PRESETS.unplacedAtRisk), }; service.addRule(optimization); optimization.configuration.conditions = []; service.reorderRules( service.rules.findIndex(({ id }) => id === optimization.id), 0, ); service.toggleRule('personal-loss'); const saved = service.rules; TestBed.tick(); vi.advanceTimersByTime(350); TestBed.resetTestingModule(); service = TestBed.inject(RuleService); expect(service.rules).toEqual(saved); expect(service.rules[0]).toMatchObject({ id: 'personal-loss', name: 'My loss priority', enabled: false, configuration: OPTIMIZATION_PRESETS.unplacedAtRisk, }); service.deleteRule('personal-loss'); expect(service.rules.some((rule) => rule.id === 'personal-loss')).toBe(false); } finally { vi.useRealTimers(); } }); });