feat: Dashboard placeholder avec preview Score Sérénité
Permet aux parents de visualiser une démo du Score Sérénité dès leur première connexion, avant même que les données réelles soient disponibles. Les autres rôles (enseignant, élève, admin) ont également leur dashboard adapté avec des sections placeholder. La landing page redirige automatiquement vers /dashboard si l'utilisateur est déjà authentifié, offrant un accès direct au tableau de bord.
This commit is contained in:
87
frontend/tests/unit/lib/data/demo-data.test.ts
Normal file
87
frontend/tests/unit/lib/data/demo-data.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import demoData from '$lib/data/demo-data.json';
|
||||
|
||||
describe('demo-data.json', () => {
|
||||
describe('serenityScore', () => {
|
||||
it('should have a value between 0 and 100', () => {
|
||||
expect(demoData.serenityScore.value).toBeGreaterThanOrEqual(0);
|
||||
expect(demoData.serenityScore.value).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
it('should have a valid emoji', () => {
|
||||
expect(['💚', '🟡', '🔴']).toContain(demoData.serenityScore.emoji);
|
||||
});
|
||||
|
||||
it('should have a valid trend', () => {
|
||||
expect(['stable', 'up', 'down']).toContain(demoData.serenityScore.trend);
|
||||
});
|
||||
|
||||
it('should have components with scores and labels', () => {
|
||||
const { components } = demoData.serenityScore;
|
||||
|
||||
expect(components).toHaveProperty('notes');
|
||||
expect(components).toHaveProperty('absences');
|
||||
expect(components).toHaveProperty('devoirs');
|
||||
|
||||
for (const [, component] of Object.entries(components)) {
|
||||
expect(component).toHaveProperty('score');
|
||||
expect(component).toHaveProperty('label');
|
||||
expect(typeof component.score).toBe('number');
|
||||
expect(typeof component.label).toBe('string');
|
||||
expect(component.score).toBeGreaterThanOrEqual(0);
|
||||
expect(component.score).toBeLessThanOrEqual(100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('schedule', () => {
|
||||
it('should have today array with schedule items', () => {
|
||||
expect(Array.isArray(demoData.schedule.today)).toBe(true);
|
||||
expect(demoData.schedule.today.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should have valid schedule items with time, subject, and room', () => {
|
||||
for (const item of demoData.schedule.today) {
|
||||
expect(item).toHaveProperty('time');
|
||||
expect(item).toHaveProperty('subject');
|
||||
expect(item).toHaveProperty('room');
|
||||
expect(typeof item.time).toBe('string');
|
||||
expect(typeof item.subject).toBe('string');
|
||||
expect(typeof item.room).toBe('string');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('grades', () => {
|
||||
it('should have recent array with grade items', () => {
|
||||
expect(Array.isArray(demoData.grades.recent)).toBe(true);
|
||||
});
|
||||
|
||||
it('should have valid grade items', () => {
|
||||
for (const item of demoData.grades.recent) {
|
||||
expect(item).toHaveProperty('subject');
|
||||
expect(item).toHaveProperty('value');
|
||||
expect(item).toHaveProperty('max');
|
||||
expect(item).toHaveProperty('date');
|
||||
expect(typeof item.value).toBe('number');
|
||||
expect(typeof item.max).toBe('number');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('homework', () => {
|
||||
it('should have upcoming array with homework items', () => {
|
||||
expect(Array.isArray(demoData.homework.upcoming)).toBe(true);
|
||||
});
|
||||
|
||||
it('should have valid homework items', () => {
|
||||
for (const item of demoData.homework.upcoming) {
|
||||
expect(item).toHaveProperty('subject');
|
||||
expect(item).toHaveProperty('title');
|
||||
expect(item).toHaveProperty('dueDate');
|
||||
expect(item).toHaveProperty('status');
|
||||
expect(['pending', 'done', 'late']).toContain(item.status);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
calculateSerenityScore,
|
||||
getSerenityEmoji,
|
||||
getSerenityLabel,
|
||||
isValidScore,
|
||||
getTrendIcon
|
||||
} from '$lib/features/dashboard/serenity-score';
|
||||
|
||||
describe('serenity-score utilities', () => {
|
||||
describe('calculateSerenityScore', () => {
|
||||
it('should calculate score correctly with all 100s', () => {
|
||||
const score = calculateSerenityScore({ notes: 100, absences: 100, devoirs: 100 });
|
||||
expect(score).toBe(100);
|
||||
});
|
||||
|
||||
it('should calculate score correctly with all 0s', () => {
|
||||
const score = calculateSerenityScore({ notes: 0, absences: 0, devoirs: 0 });
|
||||
expect(score).toBe(0);
|
||||
});
|
||||
|
||||
it('should calculate score with weighted components', () => {
|
||||
// Notes 90 × 0.4 = 36
|
||||
// Absences 95 × 0.3 = 28.5
|
||||
// Devoirs 70 × 0.3 = 21
|
||||
// Total = 85.5, rounded to 86
|
||||
const score = calculateSerenityScore({ notes: 90, absences: 95, devoirs: 70 });
|
||||
expect(score).toBe(86);
|
||||
});
|
||||
|
||||
it('should round the score to nearest integer', () => {
|
||||
const score = calculateSerenityScore({ notes: 50, absences: 50, devoirs: 50 });
|
||||
expect(score).toBe(50);
|
||||
expect(Number.isInteger(score)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSerenityEmoji', () => {
|
||||
it('should return green emoji for scores >= 70', () => {
|
||||
expect(getSerenityEmoji(70)).toBe('💚');
|
||||
expect(getSerenityEmoji(85)).toBe('💚');
|
||||
expect(getSerenityEmoji(100)).toBe('💚');
|
||||
});
|
||||
|
||||
it('should return yellow emoji for scores 40-69', () => {
|
||||
expect(getSerenityEmoji(40)).toBe('🟡');
|
||||
expect(getSerenityEmoji(55)).toBe('🟡');
|
||||
expect(getSerenityEmoji(69)).toBe('🟡');
|
||||
});
|
||||
|
||||
it('should return red emoji for scores < 40', () => {
|
||||
expect(getSerenityEmoji(0)).toBe('🔴');
|
||||
expect(getSerenityEmoji(25)).toBe('🔴');
|
||||
expect(getSerenityEmoji(39)).toBe('🔴');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSerenityLabel', () => {
|
||||
it('should return Excellent for scores >= 70', () => {
|
||||
expect(getSerenityLabel(70)).toBe('Excellent');
|
||||
expect(getSerenityLabel(100)).toBe('Excellent');
|
||||
});
|
||||
|
||||
it('should return A surveiller for scores 40-69', () => {
|
||||
expect(getSerenityLabel(40)).toBe('A surveiller');
|
||||
expect(getSerenityLabel(69)).toBe('A surveiller');
|
||||
});
|
||||
|
||||
it('should return Attention requise for scores < 40', () => {
|
||||
expect(getSerenityLabel(0)).toBe('Attention requise');
|
||||
expect(getSerenityLabel(39)).toBe('Attention requise');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidScore', () => {
|
||||
it('should return true for valid scores', () => {
|
||||
expect(isValidScore(0)).toBe(true);
|
||||
expect(isValidScore(50)).toBe(true);
|
||||
expect(isValidScore(100)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for scores below 0', () => {
|
||||
expect(isValidScore(-1)).toBe(false);
|
||||
expect(isValidScore(-100)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for scores above 100', () => {
|
||||
expect(isValidScore(101)).toBe(false);
|
||||
expect(isValidScore(200)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTrendIcon', () => {
|
||||
it('should return up arrow for up trend', () => {
|
||||
expect(getTrendIcon('up')).toBe('↑');
|
||||
});
|
||||
|
||||
it('should return down arrow for down trend', () => {
|
||||
expect(getTrendIcon('down')).toBe('↓');
|
||||
});
|
||||
|
||||
it('should return right arrow for stable trend', () => {
|
||||
expect(getTrendIcon('stable')).toBe('→');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import Page from '../../src/routes/+page.svelte';
|
||||
|
||||
describe('Home Page', () => {
|
||||
it('renders the welcome message', () => {
|
||||
render(Page);
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'Bienvenue sur Classeo' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders the description', () => {
|
||||
render(Page);
|
||||
|
||||
expect(screen.getByText('Application de gestion scolaire')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('starts counter at 0', () => {
|
||||
render(Page);
|
||||
|
||||
expect(screen.getByText('Compteur: 0')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user