Files
school_compare/nextjs-app/__tests__/lib/utils.test.ts
Tudor ff7f5487e6
Some checks failed
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 1m26s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 1m48s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
Complete Next.js migration with SSR and Docker deployment
- Migrate from vanilla JavaScript SPA to Next.js 16 with App Router
- Add server-side rendering for all pages (Home, Compare, Rankings)
- Create individual school pages with dynamic routing (/school/[urn])
- Implement Chart.js and Leaflet map integrations
- Add comprehensive SEO with sitemap, robots.txt, and JSON-LD
- Set up Docker multi-service architecture (PostgreSQL, FastAPI, Next.js)
- Update CI/CD pipeline to build both backend and frontend images
- Fix Dockerfile to include devDependencies for TypeScript compilation
- Add Jest testing configuration
- Implement performance optimizations (code splitting, caching)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 20:34:35 +00:00

105 lines
2.5 KiB
TypeScript

/**
* Utility Functions Tests
*/
import {
formatPercentage,
formatProgress,
calculateTrend,
isValidPostcode,
debounce,
} from '@/lib/utils';
describe('formatPercentage', () => {
it('formats percentages correctly', () => {
expect(formatPercentage(75.5)).toBe('75.5%');
expect(formatPercentage(100)).toBe('100.0%');
expect(formatPercentage(0)).toBe('0.0%');
});
it('handles null values', () => {
expect(formatPercentage(null)).toBe('-');
});
});
describe('formatProgress', () => {
it('formats progress scores correctly', () => {
expect(formatProgress(2.5)).toBe('+2.5');
expect(formatProgress(-1.3)).toBe('-1.3');
expect(formatProgress(0)).toBe('0.0');
});
it('handles null values', () => {
expect(formatProgress(null)).toBe('-');
});
});
describe('calculateTrend', () => {
it('calculates upward trend', () => {
expect(calculateTrend(75, 70)).toBe('up');
});
it('calculates downward trend', () => {
expect(calculateTrend(70, 75)).toBe('down');
});
it('calculates same trend', () => {
expect(calculateTrend(75, 75)).toBe('same');
});
it('handles null previous value', () => {
expect(calculateTrend(75, null)).toBe('same');
});
it('handles null current value', () => {
expect(calculateTrend(null, 75)).toBe('same');
});
});
describe('isValidPostcode', () => {
it('validates correct UK postcodes', () => {
expect(isValidPostcode('SW1A 1AA')).toBe(true);
expect(isValidPostcode('M1 1AE')).toBe(true);
expect(isValidPostcode('B33 8TH')).toBe(true);
});
it('rejects invalid postcodes', () => {
expect(isValidPostcode('INVALID')).toBe(false);
expect(isValidPostcode('12345')).toBe(false);
expect(isValidPostcode('')).toBe(false);
});
});
describe('debounce', () => {
jest.useFakeTimers();
it('delays function execution', () => {
const mockFn = jest.fn();
const debouncedFn = debounce(mockFn, 300);
debouncedFn('test');
expect(mockFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(300);
expect(mockFn).toHaveBeenCalledWith('test');
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('cancels previous calls', () => {
const mockFn = jest.fn();
const debouncedFn = debounce(mockFn, 300);
debouncedFn('first');
jest.advanceTimersByTime(150);
debouncedFn('second');
jest.advanceTimersByTime(150);
debouncedFn('third');
jest.advanceTimersByTime(300);
expect(mockFn).toHaveBeenCalledWith('third');
expect(mockFn).toHaveBeenCalledTimes(1);
});
jest.useRealTimers();
});