Files
portfolio-next/test/core/courses.test.ts
T
Lucas WintherandClaude Opus 5.5 249e4a8017 feat(core): course summary and search
courseSummary groups courses by topic, ordered by course time;
courseMatches searches title, provider, topic and keywords;
formatHours and monthYear format the numbers the page shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 10:06:29 +01:00

59 lines
2.4 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import { courseMatches, courseSummary, formatHours, monthYear, type CourseLike } from '../../src/core/courses.ts';
const c = (over: Partial<CourseLike>): CourseLike => ({
title: 'T', provider: 'P', kind: 'course', topic: 'A', completed: '2025-03-01', minutes: 60, certificate: false,
keywords: [], ...over,
});
describe('courseSummary', () => {
test('counts, certificates, minutes, earliest month and topics by time', () => {
const s = courseSummary([
c({ topic: 'Beta', minutes: 100, certificate: true, completed: '2025-04-02' }),
c({ topic: 'Alpha', minutes: 30, kind: 'lab', completed: '2025-02-20' }),
c({ topic: 'Alpha', minutes: 70, certificate: true, completed: '2025-05-01' }),
c({ topic: 'Gamma', minutes: 300, kind: 'lab', certificate: true, completed: '2025-06-01' }),
]);
expect(s).toEqual({
count: 4, certificates: 3, minutes: 500, since: '2025-02',
topics: [
{ topic: 'Gamma', courses: 0, labs: 1, certificates: 1, minutes: 300 },
{ topic: 'Alpha', courses: 1, labs: 1, certificates: 1, minutes: 100 },
{ topic: 'Beta', courses: 1, labs: 0, certificates: 1, minutes: 100 },
],
});
});
test('no courses', () => {
expect(courseSummary([])).toEqual({ count: 0, certificates: 0, minutes: 0, since: '', topics: [] });
});
});
describe('courseMatches', () => {
const course = c({ title: 'Containers from Scratch', provider: 'Sample School', topic: 'Docker', keywords: ['Images'] });
test('case-insensitive over title, provider, topic and keywords', () => {
expect(courseMatches(course, 'scratch')).toBe(true);
expect(courseMatches(course, 'SAMPLE')).toBe(true);
expect(courseMatches(course, 'docker')).toBe(true);
expect(courseMatches(course, 'images')).toBe(true);
expect(courseMatches(course, ' images ')).toBe(true);
expect(courseMatches(course, 'kotlin')).toBe(false);
});
test('an empty query matches nothing', () => {
expect(courseMatches(course, '')).toBe(false);
expect(courseMatches(course, ' ')).toBe(false);
});
});
test('formatHours gives one decimal', () => {
expect(formatHours(564)).toBe('9.4h');
expect(formatHours(24)).toBe('0.4h');
expect(formatHours(60)).toBe('1.0h');
});
test('monthYear', () => {
expect(monthYear('2025-02')).toBe('Feb 2025');
expect(monthYear('2024-12-31')).toBe('Dec 2024');
expect(monthYear('')).toBe('');
});