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>
This commit is contained in:
104
nextjs-app/__tests__/lib/utils.test.ts
Normal file
104
nextjs-app/__tests__/lib/utils.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
Reference in New Issue
Block a user