Replace Vitest with bun test across the whole suite. A preload compiles components through Angular's JIT transform with inlined templates, sets up jsdom, TestBed and zoneless change detection, and a small shim covers the Vitest APIs bun:test lacks. Files run isolated, real-worker specs spawn the running Bun, and line coverage from lcov is converted to the existing HTML and Cobertura reports. The CI benchmark replaces its heap cap with a container memory limit. Vitest, Jest, Nx and their Vite configuration are removed. Refs #291 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
186 lines
5.2 KiB
TypeScript
186 lines
5.2 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|