removeEmployee now also filters the deleted employee's history entries, so no orphaned "(unbekannt)" rows remain. Adds a test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
184 lines
6.9 KiB
JavaScript
184 lines
6.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { addHistoryEntries, removeHistoryEntry, loadFromJSON, saveToJSON, normalizeEmployees, removeEmployee } from '../js/data.js';
|
|
|
|
function makeState(history = []) {
|
|
return {
|
|
employees: [],
|
|
calendar: { holidays: [], companyClosures: [] },
|
|
history: [...history]
|
|
};
|
|
}
|
|
|
|
// addHistoryEntries: two entries with same date but different employees both kept
|
|
{
|
|
const state = makeState();
|
|
addHistoryEntries(state, [
|
|
{ date: '2026-04-01', employeeId: 'alice', manual: false },
|
|
{ date: '2026-04-01', employeeId: 'bob', manual: false }
|
|
]);
|
|
assert.equal(state.history.length, 2, 'two distinct entries kept on same date');
|
|
const ids = state.history.map(h => h.employeeId).sort();
|
|
assert.deepEqual(ids, ['alice', 'bob'], 'both employees present');
|
|
}
|
|
|
|
// addHistoryEntries: same (date, employeeId) replaced, others on same date untouched
|
|
{
|
|
const state = makeState([
|
|
{ date: '2026-04-01', employeeId: 'alice', manual: false },
|
|
{ date: '2026-04-01', employeeId: 'bob', manual: false }
|
|
]);
|
|
addHistoryEntries(state, [
|
|
{ date: '2026-04-01', employeeId: 'alice', manual: true }
|
|
]);
|
|
assert.equal(state.history.length, 2, 'still two entries');
|
|
const alice = state.history.find(h => h.employeeId === 'alice');
|
|
assert.equal(alice.manual, true, 'alice replaced with manual=true');
|
|
const bob = state.history.find(h => h.employeeId === 'bob');
|
|
assert.equal(bob.manual, false, 'bob untouched');
|
|
}
|
|
|
|
// addHistoryEntries: empty input → no change
|
|
{
|
|
const state = makeState([{ date: '2026-04-01', employeeId: 'alice', manual: false }]);
|
|
addHistoryEntries(state, []);
|
|
assert.equal(state.history.length, 1, 'empty entries → no change');
|
|
}
|
|
|
|
// removeHistoryEntry without employeeId: removes all entries on that date
|
|
{
|
|
const state = makeState([
|
|
{ date: '2026-04-01', employeeId: 'alice', manual: false },
|
|
{ date: '2026-04-01', employeeId: 'bob', manual: false },
|
|
{ date: '2026-04-02', employeeId: 'alice', manual: false }
|
|
]);
|
|
removeHistoryEntry(state, '2026-04-01');
|
|
assert.equal(state.history.length, 1, 'only 2026-04-02 alice remains');
|
|
assert.equal(state.history[0].date, '2026-04-02', 'remaining entry is 2026-04-02');
|
|
}
|
|
|
|
// removeHistoryEntry with employeeId: removes only that one entry
|
|
{
|
|
const state = makeState([
|
|
{ date: '2026-04-01', employeeId: 'alice', manual: false },
|
|
{ date: '2026-04-01', employeeId: 'bob', manual: false }
|
|
]);
|
|
removeHistoryEntry(state, '2026-04-01', 'alice');
|
|
assert.equal(state.history.length, 1, 'one entry remains');
|
|
assert.equal(state.history[0].employeeId, 'bob', 'bob still there');
|
|
}
|
|
|
|
// removeHistoryEntry with employeeId that does not exist: no-op
|
|
{
|
|
const state = makeState([
|
|
{ date: '2026-04-01', employeeId: 'alice', manual: false }
|
|
]);
|
|
removeHistoryEntry(state, '2026-04-01', 'nonexistent');
|
|
assert.equal(state.history.length, 1, 'no change when employeeId not found');
|
|
}
|
|
|
|
// loadFromJSON: missing settings → default {moderatorsPerDay: 1}
|
|
{
|
|
const json = JSON.stringify({
|
|
employees: [],
|
|
calendar: { holidays: [], companyClosures: [] },
|
|
history: []
|
|
});
|
|
const loaded = loadFromJSON(json);
|
|
assert.deepEqual(loaded.settings, { moderatorsPerDay: 1 }, 'default settings when missing');
|
|
}
|
|
|
|
// loadFromJSON: present settings preserved, moderatorsPerDay clamped to [1,5]
|
|
{
|
|
const json = JSON.stringify({
|
|
employees: [], calendar: { holidays: [], companyClosures: [] }, history: [],
|
|
settings: { moderatorsPerDay: 3 }
|
|
});
|
|
const loaded = loadFromJSON(json);
|
|
assert.equal(loaded.settings.moderatorsPerDay, 3, 'preserved value');
|
|
}
|
|
|
|
// loadFromJSON: invalid moderatorsPerDay (string, out of range) → fallback to 1
|
|
{
|
|
const tooHigh = loadFromJSON(JSON.stringify({
|
|
employees: [], calendar: { holidays: [], companyClosures: [] }, history: [],
|
|
settings: { moderatorsPerDay: 99 }
|
|
}));
|
|
assert.equal(tooHigh.settings.moderatorsPerDay, 1, 'out of range falls back to 1');
|
|
|
|
const wrongType = loadFromJSON(JSON.stringify({
|
|
employees: [], calendar: { holidays: [], companyClosures: [] }, history: [],
|
|
settings: { moderatorsPerDay: 'two' }
|
|
}));
|
|
assert.equal(wrongType.settings.moderatorsPerDay, 1, 'wrong type falls back to 1');
|
|
}
|
|
|
|
// saveToJSON: settings are serialized
|
|
{
|
|
const state = {
|
|
employees: [], calendar: { holidays: [], companyClosures: [] }, history: [],
|
|
settings: { moderatorsPerDay: 2 }
|
|
};
|
|
const json = saveToJSON(state);
|
|
const parsed = JSON.parse(json);
|
|
assert.deepEqual(parsed.settings, { moderatorsPerDay: 2 }, 'settings round-trip');
|
|
}
|
|
|
|
// normalizeEmployees: missing constraints are filled with defaults
|
|
{
|
|
const out = normalizeEmployees([{ id: 'a', name: 'Alice' }]);
|
|
assert.equal(out.length, 1, 'employee kept');
|
|
assert.deepEqual(out[0].constraints.neverDays, [], 'neverDays defaulted');
|
|
assert.equal(out[0].constraints.minGapDays, 14, 'minGapDays defaulted to 14');
|
|
assert.equal(out[0].constraints.maxPerMonth, null, 'maxPerMonth defaulted to null');
|
|
}
|
|
|
|
// normalizeEmployees: rows without a usable name are dropped
|
|
{
|
|
const out = normalizeEmployees([{ id: 'a', name: ' ' }, { id: 'b' }, null, 42]);
|
|
assert.equal(out.length, 0, 'nameless/invalid rows dropped');
|
|
}
|
|
|
|
// normalizeEmployees: duplicate ids are de-duplicated to distinct ids
|
|
{
|
|
const out = normalizeEmployees([{ id: 'dup', name: 'A' }, { id: 'dup', name: 'B' }]);
|
|
assert.equal(out.length, 2, 'both kept');
|
|
assert.notEqual(out[0].id, out[1].id, 'duplicate id resolved');
|
|
}
|
|
|
|
// normalizeEmployees: invalid vacation ranges dropped, valid kept
|
|
{
|
|
const out = normalizeEmployees([{ id: 'a', name: 'A', constraints: { vacations: [
|
|
{ from: '2026-05-10', to: '2026-05-05' }, // reversed → dropped
|
|
{ from: '2026-06-01', to: '2026-06-07' } // valid → kept
|
|
] } }]);
|
|
assert.equal(out[0].constraints.vacations.length, 1, 'only valid vacation kept');
|
|
assert.equal(out[0].constraints.vacations[0].from, '2026-06-01', 'valid vacation preserved');
|
|
}
|
|
|
|
// loadFromJSON: employees without constraints are normalized (no later crash)
|
|
{
|
|
const loaded = loadFromJSON(JSON.stringify({
|
|
employees: [{ id: 'x', name: 'NoConstraints' }],
|
|
calendar: { holidays: [], companyClosures: [] }, history: []
|
|
}));
|
|
assert.ok(Array.isArray(loaded.employees[0].constraints.neverDays), 'constraints present after load');
|
|
}
|
|
|
|
// removeEmployee: also removes the employee's history entries (no orphans)
|
|
{
|
|
const state = {
|
|
employees: [{ id: 'a', name: 'Alice' }, { id: 'b', name: 'Bob' }],
|
|
calendar: { holidays: [], companyClosures: [] },
|
|
history: [
|
|
{ date: '2026-04-01', employeeId: 'a', manual: false },
|
|
{ date: '2026-04-02', employeeId: 'b', manual: false }
|
|
]
|
|
};
|
|
removeEmployee(state, 'a');
|
|
assert.equal(state.employees.length, 1, 'alice removed from employees');
|
|
assert.ok(!state.history.some(h => h.employeeId === 'a'), 'alice history removed');
|
|
assert.equal(state.history.length, 1, 'bob history untouched');
|
|
}
|
|
|
|
console.log('All data tests passed.');
|