mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 22:01:24 +02:00
✨ feat(nutriphi): prepare for production release with tests and improved UX
- Remove broken header links to non-existent settings/profile pages
- Replace header links with settings page link
- Remove TODO comments for credit system in analysis controller
- Add comprehensive error handling with German messages in meals store
- Add loading states, retry buttons, and error displays in UI components
- Create new settings page with daily goals editor
- Add 99 tests across backend, web, and shared packages:
- Backend: MealService, GoalsService, StatsService, FavoritesService,
RecommendationsService, nutrition.utils (Jest)
- Web: API client tests with mocks (Vitest)
- Shared: utility function tests (Vitest)
- Set up test infrastructure (Jest for NestJS, Vitest for SvelteKit)
This commit is contained in:
parent
ee630158c5
commit
3ff8d3833b
28 changed files with 2470 additions and 119 deletions
189
apps/nutriphi/packages/shared/src/utils/utils.spec.ts
Normal file
189
apps/nutriphi/packages/shared/src/utils/utils.spec.ts
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
calculateProgress,
|
||||
sumNutrition,
|
||||
formatNutrient,
|
||||
getProgressColor,
|
||||
detectDeficiencies,
|
||||
suggestMealType,
|
||||
formatDateForDisplay,
|
||||
isToday,
|
||||
} from './index';
|
||||
|
||||
describe('Shared Utils', () => {
|
||||
describe('calculateProgress', () => {
|
||||
it('should calculate progress with default values', () => {
|
||||
const nutrition = { calories: 1000, protein: 25, carbohydrates: 137, fat: 39 };
|
||||
const progress = calculateProgress(nutrition);
|
||||
|
||||
expect(progress.calories.current).toBe(1000);
|
||||
expect(progress.calories.target).toBe(2000);
|
||||
expect(progress.calories.percentage).toBe(50);
|
||||
});
|
||||
|
||||
it('should use custom goals', () => {
|
||||
const nutrition = { calories: 1500, protein: 75 };
|
||||
const goals = {
|
||||
dailyCalories: 3000,
|
||||
dailyProtein: 150,
|
||||
dailyCarbs: 300,
|
||||
dailyFat: 100,
|
||||
} as any;
|
||||
|
||||
const progress = calculateProgress(nutrition, goals);
|
||||
|
||||
expect(progress.calories.target).toBe(3000);
|
||||
expect(progress.calories.percentage).toBe(50);
|
||||
});
|
||||
|
||||
it('should cap percentage at 100', () => {
|
||||
const nutrition = { calories: 3000 };
|
||||
const progress = calculateProgress(nutrition);
|
||||
|
||||
expect(progress.calories.percentage).toBe(100);
|
||||
});
|
||||
|
||||
it('should handle missing values', () => {
|
||||
const progress = calculateProgress({});
|
||||
|
||||
expect(progress.calories.current).toBe(0);
|
||||
expect(progress.calories.percentage).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sumNutrition', () => {
|
||||
it('should sum multiple meals', () => {
|
||||
const meals = [
|
||||
{ nutrition: { calories: 500, protein: 20, carbohydrates: 60, fat: 15 } },
|
||||
{ nutrition: { calories: 300, protein: 15, carbohydrates: 40, fat: 10 } },
|
||||
];
|
||||
|
||||
const sum = sumNutrition(meals);
|
||||
|
||||
expect(sum.calories).toBe(800);
|
||||
expect(sum.protein).toBe(35);
|
||||
expect(sum.carbohydrates).toBe(100);
|
||||
expect(sum.fat).toBe(25);
|
||||
});
|
||||
|
||||
it('should handle null nutrition', () => {
|
||||
const meals = [{ nutrition: { calories: 500 } }, { nutrition: null }];
|
||||
|
||||
const sum = sumNutrition(meals);
|
||||
|
||||
expect(sum.calories).toBe(500);
|
||||
});
|
||||
|
||||
it('should handle empty array', () => {
|
||||
const sum = sumNutrition([]);
|
||||
|
||||
expect(sum.calories).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatNutrient', () => {
|
||||
it('should format calories', () => {
|
||||
expect(formatNutrient('calories', 1234.5)).toBe('1235 kcal');
|
||||
});
|
||||
|
||||
it('should format protein', () => {
|
||||
expect(formatNutrient('protein', 25.5)).toBe('25.5 g');
|
||||
});
|
||||
|
||||
it('should return dash for undefined', () => {
|
||||
expect(formatNutrient('calories', undefined)).toBe('-');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProgressColor', () => {
|
||||
it('should return red for low percentage', () => {
|
||||
expect(getProgressColor(30)).toBe('#EF4444');
|
||||
});
|
||||
|
||||
it('should return orange for medium percentage', () => {
|
||||
expect(getProgressColor(60)).toBe('#F59E0B');
|
||||
});
|
||||
|
||||
it('should return green for high percentage', () => {
|
||||
expect(getProgressColor(90)).toBe('#22C55E');
|
||||
});
|
||||
|
||||
it('should return red for over 100%', () => {
|
||||
expect(getProgressColor(120)).toBe('#EF4444');
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectDeficiencies', () => {
|
||||
it('should detect low protein', () => {
|
||||
const nutrition = { protein: 10 }; // 20% of 50g target
|
||||
const deficiencies = detectDeficiencies(nutrition);
|
||||
|
||||
expect(deficiencies).toContainEqual(expect.objectContaining({ nutrient: 'protein' }));
|
||||
});
|
||||
|
||||
it('should not detect deficiency when above threshold', () => {
|
||||
const nutrition = { protein: 30 }; // 60% of target
|
||||
const deficiencies = detectDeficiencies(nutrition);
|
||||
|
||||
expect(deficiencies.find((d) => d.nutrient === 'protein')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('suggestMealType', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should suggest breakfast in the morning', () => {
|
||||
vi.setSystemTime(new Date('2024-01-15T08:00:00'));
|
||||
expect(suggestMealType()).toBe('breakfast');
|
||||
});
|
||||
|
||||
it('should suggest lunch at noon', () => {
|
||||
vi.setSystemTime(new Date('2024-01-15T12:00:00'));
|
||||
expect(suggestMealType()).toBe('lunch');
|
||||
});
|
||||
|
||||
it('should suggest dinner in the evening', () => {
|
||||
vi.setSystemTime(new Date('2024-01-15T19:00:00'));
|
||||
expect(suggestMealType()).toBe('dinner');
|
||||
});
|
||||
|
||||
it('should suggest snack at other times', () => {
|
||||
vi.setSystemTime(new Date('2024-01-15T15:00:00'));
|
||||
expect(suggestMealType()).toBe('snack');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatDateForDisplay', () => {
|
||||
it('should format date in German', () => {
|
||||
const date = new Date('2024-01-15');
|
||||
const formatted = formatDateForDisplay(date, 'de-DE');
|
||||
|
||||
expect(formatted).toContain('15');
|
||||
expect(formatted).toContain('Januar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isToday', () => {
|
||||
it('should return true for today', () => {
|
||||
expect(isToday(new Date())).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for yesterday', () => {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
expect(isToday(yesterday)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for same day different year', () => {
|
||||
const lastYear = new Date();
|
||||
lastYear.setFullYear(lastYear.getFullYear() - 1);
|
||||
expect(isToday(lastYear)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue