First working prototype
This commit is contained in:
107
src/lib/resume.test.js
Normal file
107
src/lib/resume.test.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildResume, resumeToText } from './resume.js';
|
||||
import { atLocalTime, MINUTE } from './time.js';
|
||||
|
||||
const DATE = '2026-09-04';
|
||||
const at = (hhmm) => atLocalTime(DATE, hhmm);
|
||||
|
||||
const DAY = {
|
||||
date: DATE,
|
||||
status: 'closed',
|
||||
lunchType: 'restaurant',
|
||||
restaurant: 'Le Relais',
|
||||
endedTs: at('17:30'),
|
||||
};
|
||||
|
||||
const ENTRIES = [
|
||||
{ id: 1, date: DATE, seq: 0, kind: 'travel', task: 'Chargement / transport', client: 'Dupont SA', startTs: at('07:30'), endTs: at('08:15') },
|
||||
{ id: 2, date: DATE, seq: 1, kind: 'work', task: 'Terrassement', client: 'Dupont SA', startTs: at('08:15'), endTs: at('12:00') },
|
||||
{ id: 3, date: DATE, seq: 2, kind: 'lunch', task: 'Pause déjeuner', client: null, startTs: at('12:00'), endTs: at('13:00') },
|
||||
{ id: 4, date: DATE, seq: 3, kind: 'work', task: 'Terrassement', client: 'Dupont SA', startTs: at('13:00'), endTs: at('16:45') },
|
||||
{ id: 5, date: DATE, seq: 4, kind: 'work', task: 'Retour / déchargement', client: 'Dupont SA', startTs: at('16:45'), endTs: at('17:30') },
|
||||
];
|
||||
|
||||
describe('buildResume', () => {
|
||||
it('groups by client and sums repeated tasks', () => {
|
||||
const r = buildResume(DAY, ENTRIES, 15);
|
||||
expect(r.clients).toHaveLength(1);
|
||||
const [dupont] = r.clients;
|
||||
expect(dupont.name).toBe('Dupont SA');
|
||||
// Terrassement appears twice, before and after lunch, and reads as one line.
|
||||
expect(dupont.tasks.map((t) => t.name)).toEqual([
|
||||
'Terrassement',
|
||||
'Chargement / transport',
|
||||
'Retour / déchargement',
|
||||
]);
|
||||
expect(dupont.tasks[0].ms).toBe(450 * MINUTE); // 3h45 + 3h45
|
||||
});
|
||||
|
||||
it('keeps lunch out of the worked total', () => {
|
||||
const r = buildResume(DAY, ENTRIES, 15);
|
||||
expect(r.workedMs).toBe(9 * 60 * MINUTE); // 09h00 on site, lunch excluded
|
||||
expect(r.lunch).toEqual({ type: 'restaurant', restaurant: 'Le Relais', ms: 60 * MINUTE });
|
||||
});
|
||||
|
||||
it('splits totals across several clients', () => {
|
||||
const mixed = [
|
||||
ENTRIES[0],
|
||||
{ ...ENTRIES[1], client: 'Martin & Fils' },
|
||||
...ENTRIES.slice(2),
|
||||
];
|
||||
const r = buildResume(DAY, mixed, 15);
|
||||
expect(r.clients.map((c) => c.name)).toEqual(['Dupont SA', 'Martin & Fils']);
|
||||
expect(r.workedMs).toBe(9 * 60 * MINUTE);
|
||||
});
|
||||
|
||||
it('omits entries shorter than one rounding step, and says so', () => {
|
||||
const withBlip = [
|
||||
...ENTRIES,
|
||||
{ id: 6, date: DATE, seq: 5, kind: 'work', task: 'Mauvais appui', client: 'Dupont SA', startTs: at('17:30'), endTs: at('17:32') },
|
||||
];
|
||||
const r = buildResume(DAY, withBlip, 15);
|
||||
expect(r.omitted).toBe(1);
|
||||
expect(r.clients[0].tasks.map((t) => t.name)).not.toContain('Mauvais appui');
|
||||
});
|
||||
|
||||
it('keeps a 2-minute entry when rounding is set to 1 minute', () => {
|
||||
const withBlip = [
|
||||
...ENTRIES,
|
||||
{ id: 6, date: DATE, seq: 5, kind: 'work', task: 'Mauvais appui', client: 'Dupont SA', startTs: at('17:30'), endTs: at('17:32') },
|
||||
];
|
||||
const r = buildResume(DAY, withBlip, 1);
|
||||
expect(r.omitted).toBe(0);
|
||||
expect(r.clients[0].tasks.map((t) => t.name)).toContain('Mauvais appui');
|
||||
});
|
||||
|
||||
it('returns null without a day', () => {
|
||||
expect(buildResume(null, [], 15)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('resumeToText', () => {
|
||||
it('renders the approved logbook format', () => {
|
||||
expect(resumeToText(buildResume(DAY, ENTRIES, 15))).toBe(
|
||||
[
|
||||
'Vendredi 4 septembre',
|
||||
'',
|
||||
'Dupont SA — 9h00',
|
||||
' Terrassement 7h30',
|
||||
' Chargement / transport 0h45',
|
||||
' Retour / déchargement 0h45',
|
||||
'',
|
||||
'Déjeuner : restaurant (Le Relais), 1h00',
|
||||
'',
|
||||
'Total travaillé : 9h00',
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('names the panier case without a restaurant', () => {
|
||||
const r = buildResume({ ...DAY, lunchType: 'panier', restaurant: null }, ENTRIES, 15);
|
||||
expect(resumeToText(r)).toContain('Déjeuner : panier, 1h00');
|
||||
});
|
||||
|
||||
it('is empty for a null resume', () => {
|
||||
expect(resumeToText(null)).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user