feat: Permettre aux élèves de consulter leur emploi du temps

Les élèves n'avaient aucun moyen de voir leur emploi du temps
depuis l'application. Cette fonctionnalité ajoute une page dédiée
avec deux modes de visualisation (jour et semaine), la navigation
temporelle, et le détail des cours au tap.

Le backend résout l'EDT de l'élève en chaînant : affectation classe →
créneaux récurrents + exceptions + calendrier scolaire → enrichissement
des noms (matières/enseignants). Le frontend utilise un cache offline
(Workbox NetworkFirst) pour rester consultable hors connexion.
This commit is contained in:
2026-03-05 16:21:37 +01:00
parent ae640e91ac
commit 36ceefb625
30 changed files with 3526 additions and 30 deletions

View File

@@ -0,0 +1,70 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock $app/environment
vi.mock('$app/environment', () => ({
browser: true
}));
import { isOffline, recordSync, getLastSyncDate, prefetchScheduleDays } from '$lib/features/schedule/stores/scheduleCache';
describe('scheduleCache', () => {
beforeEach(() => {
localStorage.clear();
});
describe('isOffline', () => {
it('returns false when navigator is online', () => {
Object.defineProperty(navigator, 'onLine', { value: true, configurable: true });
expect(isOffline()).toBe(false);
});
it('returns true when navigator is offline', () => {
Object.defineProperty(navigator, 'onLine', { value: false, configurable: true });
expect(isOffline()).toBe(true);
});
});
describe('sync tracking', () => {
it('returns null when no sync has been recorded', () => {
expect(getLastSyncDate()).toBeNull();
});
it('records and retrieves the last sync date', () => {
recordSync();
const date = getLastSyncDate();
expect(date).not.toBeNull();
expect(new Date(date!).getTime()).toBeGreaterThan(0);
});
});
describe('prefetchScheduleDays', () => {
it('prefetches 7 past days + today + 23 future days (31 total)', async () => {
const fetchFn = vi.fn().mockResolvedValue({});
const today = new Date('2026-03-10');
await prefetchScheduleDays(fetchFn, today);
// 7 past + 1 today + 23 future = 31 calls
expect(fetchFn).toHaveBeenCalledTimes(31);
// First call: 7 days ago
expect(fetchFn).toHaveBeenCalledWith('2026-03-03');
// Today
expect(fetchFn).toHaveBeenCalledWith('2026-03-10');
// Last call: 23 days ahead
expect(fetchFn).toHaveBeenCalledWith('2026-04-02');
});
it('silently handles fetch failures', async () => {
const fetchFn = vi.fn()
.mockResolvedValueOnce({})
.mockRejectedValueOnce(new Error('Network error'))
.mockResolvedValue({});
await expect(
prefetchScheduleDays(fetchFn, new Date('2026-03-02'))
).resolves.toBeUndefined();
expect(fetchFn).toHaveBeenCalledTimes(31);
});
});
});