101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import {
|
|
AdminChallengeListItem,
|
|
ImportPreviewResponse,
|
|
ImportResultResponse,
|
|
} from '../../frontend/src/app/core/services/admin.service';
|
|
import {
|
|
buildExportFilename,
|
|
categoryIconSrc,
|
|
deriveEmptyState,
|
|
difficultyClass,
|
|
formatFileSize,
|
|
summarizeImportPreview,
|
|
summarizeImportResult,
|
|
} from '../../frontend/src/app/features/admin/challenges/challenges.pure';
|
|
|
|
function makeRow(overrides: Partial<AdminChallengeListItem> = {}): AdminChallengeListItem {
|
|
return {
|
|
id: 'c1',
|
|
name: 'Alpha',
|
|
categoryAbbreviation: 'CRY',
|
|
categoryIconPath: '/uploads/icons/CRY.png',
|
|
difficulty: 'LOW',
|
|
protocol: 'WEB',
|
|
enabled: true,
|
|
createdAt: '2026-07-22T00:00:00.000Z',
|
|
updatedAt: '2026-07-22T00:00:00.000Z',
|
|
fileCount: 0,
|
|
solveCount: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('challenges.pure helpers', () => {
|
|
it('formatFileSize formats bytes / KB / MB', () => {
|
|
expect(formatFileSize(0)).toBe('0 B');
|
|
expect(formatFileSize(512)).toBe('512 B');
|
|
expect(formatFileSize(2048)).toMatch(/KB$/);
|
|
expect(formatFileSize(5 * 1024 * 1024)).toMatch(/MB$/);
|
|
});
|
|
|
|
it('buildExportFilename derives an ISO-date filename', () => {
|
|
const fixed = new Date('2026-07-22T12:00:00.000Z');
|
|
expect(buildExportFilename(fixed)).toBe('challenges-export-2026-07-22.json');
|
|
});
|
|
|
|
it('deriveEmptyState distinguishes empty-list, no-results-search, and loaded states', () => {
|
|
expect(deriveEmptyState([], '', false)).toEqual({ kind: 'empty-list' });
|
|
expect(deriveEmptyState([], 'crypto', false)).toEqual({
|
|
kind: 'no-results-search',
|
|
search: 'crypto',
|
|
});
|
|
expect(deriveEmptyState([makeRow()], '', false)).toBeNull();
|
|
expect(deriveEmptyState([], '', true)).toBeNull();
|
|
});
|
|
|
|
it('summarizeImportPreview builds a single-line preview summary', () => {
|
|
const preview: ImportPreviewResponse = {
|
|
total: 3,
|
|
conflicts: ['Alpha', 'Beta'],
|
|
unknownCategories: ['UNKNOWN'],
|
|
warnings: [],
|
|
};
|
|
const out = summarizeImportPreview(preview);
|
|
expect(out).toContain('Import 3 challenges');
|
|
expect(out).toContain('overwritten');
|
|
expect(out).toContain('missing categories');
|
|
});
|
|
|
|
it('summarizeImportPreview handles empty document', () => {
|
|
const preview: ImportPreviewResponse = {
|
|
total: 0,
|
|
conflicts: [],
|
|
unknownCategories: [],
|
|
warnings: [],
|
|
};
|
|
expect(summarizeImportPreview(preview)).toContain('no challenges');
|
|
});
|
|
|
|
it('summarizeImportResult reports imported/skipped/warning counts', () => {
|
|
const r: ImportResultResponse = {
|
|
imported: 2,
|
|
skipped: [{ name: 'X', reason: 'Unknown category: Y' }],
|
|
warnings: ['1 skipped.'],
|
|
};
|
|
const out = summarizeImportResult(r);
|
|
expect(out).toContain('Imported 2');
|
|
expect(out).toContain('Skipped 1');
|
|
expect(out).toContain('1 warning');
|
|
});
|
|
|
|
it('difficultyClass returns the appropriate badge class', () => {
|
|
expect(difficultyClass('LOW')).toContain('difficulty-low');
|
|
expect(difficultyClass('MEDIUM')).toContain('difficulty-medium');
|
|
expect(difficultyClass('HIGH')).toContain('difficulty-high');
|
|
});
|
|
|
|
it('categoryIconSrc returns the supplied icon path or an empty string', () => {
|
|
expect(categoryIconSrc('/uploads/icons/X.png', 'X')).toBe('/uploads/icons/X.png');
|
|
expect(categoryIconSrc('', 'X')).toBe('');
|
|
});
|
|
}); |