Files
HIPCTF2/tests/frontend/admin-categories-form-modal.spec.ts
T
2026-07-22 18:53:11 +00:00

188 lines
6.5 KiB
TypeScript

import '@angular/compiler';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import {
syncCategoryForm,
CategoryFormMode,
} from '../../frontend/src/app/features/admin/categories/category-form-modal.component';
import { AdminCategory } from '../../frontend/src/app/core/services/admin.service';
function buildForm(): FormGroup<{
name: FormControl<string>;
abbreviation: FormControl<string>;
description: FormControl<string>;
}> {
const fb = new FormBuilder();
return fb.nonNullable.group({
name: fb.nonNullable.control('', []),
abbreviation: fb.nonNullable.control('', []),
description: fb.nonNullable.control(''),
});
}
function makeRow(overrides: Partial<AdminCategory> = {}): AdminCategory {
return {
id: 'row-1',
name: 'Cryptography',
abbreviation: 'CRY',
description: 'Cryptographic challenges',
iconPath: '',
isSystem: true,
systemKey: 'CRY',
createdAt: '',
updatedAt: '',
...overrides,
};
}
describe('syncCategoryForm - edit prefill', () => {
it('populates name, abbreviation, description for a system row and locks abbreviation', () => {
const form = buildForm();
const result = syncCategoryForm(form, 'edit', makeRow());
expect(form.controls.name.value).toBe('Cryptography');
expect(form.controls.abbreviation.value).toBe('CRY');
expect(form.controls.description.value).toBe('Cryptographic challenges');
expect(result.abbreviationReadonly).toBe(true);
expect(result.iconPreview).toBeNull();
});
it('does not lock abbreviation for a user (non-system) row', () => {
const form = buildForm();
const result = syncCategoryForm(
form,
'edit',
makeRow({
name: 'Misc',
abbreviation: 'MSC',
description: 'Other',
isSystem: false,
systemKey: null,
}),
);
expect(form.controls.name.value).toBe('Misc');
expect(form.controls.abbreviation.value).toBe('MSC');
expect(form.controls.description.value).toBe('Other');
expect(result.abbreviationReadonly).toBe(false);
});
it('re-populates when invoked a second time with a different category', () => {
const form = buildForm();
syncCategoryForm(
form,
'edit',
makeRow({ name: 'Alpha', abbreviation: 'AAA', description: 'first', isSystem: false, systemKey: null }),
);
expect(form.controls.name.value).toBe('Alpha');
expect(form.controls.abbreviation.value).toBe('AAA');
const result = syncCategoryForm(
form,
'edit',
makeRow({ name: 'Bravo', abbreviation: 'BBB', description: 'second', isSystem: false, systemKey: null }),
);
expect(form.controls.name.value).toBe('Bravo');
expect(form.controls.abbreviation.value).toBe('BBB');
expect(form.controls.description.value).toBe('second');
expect(result.abbreviationReadonly).toBe(false);
});
it('clears the form when invoked with mode="create" and a null category', () => {
const form = buildForm();
syncCategoryForm(form, 'edit', makeRow());
expect(form.controls.name.value).toBe('Cryptography');
const result = syncCategoryForm(form, 'create', null);
expect(form.controls.name.value).toBe('');
expect(form.controls.abbreviation.value).toBe('');
expect(form.controls.description.value).toBe('');
expect(result.abbreviationReadonly).toBe(false);
expect(result.iconPreview).toBeNull();
});
it('marks controls as pristine and untouched after prefill', () => {
const form = buildForm();
syncCategoryForm(form, 'edit', makeRow());
expect(form.controls.name.pristine).toBe(true);
expect(form.controls.name.touched).toBe(false);
expect(form.controls.abbreviation.pristine).toBe(true);
expect(form.controls.abbreviation.touched).toBe(false);
expect(form.controls.description.pristine).toBe(true);
expect(form.controls.description.touched).toBe(false);
});
it('returns iconPreview from category.iconPath when set', () => {
const form = buildForm();
const result = syncCategoryForm(
form,
'edit',
makeRow({ iconPath: '/uploads/icons/cry.png' }),
);
expect(result.iconPreview).toBe('/uploads/icons/cry.png');
});
});
describe('CategoryFormModalComponent - prefill contract', () => {
it('exposes a form group with name, abbreviation, description controls', async () => {
const { CategoryFormModalComponent } = await import(
'../../frontend/src/app/features/admin/categories/category-form-modal.component'
);
const fb = new FormBuilder();
const component = Object.create(CategoryFormModalComponent.prototype) as any;
component.fb = fb;
component.form = fb.nonNullable.group({
name: fb.nonNullable.control('', []),
abbreviation: fb.nonNullable.control('', []),
description: fb.nonNullable.control(''),
});
expect(component.form.controls.name).toBeDefined();
expect(component.form.controls.abbreviation).toBeDefined();
expect(component.form.controls.description).toBeDefined();
expect(component.form.controls.name.value).toBe('');
expect(component.form.controls.abbreviation.value).toBe('');
expect(component.form.controls.description.value).toBe('');
});
it('prefills controls and locks abbreviation for a system row via syncCategoryForm', () => {
const form = buildForm();
const result = syncCategoryForm(form, 'edit', makeRow());
expect(form.controls.name.value).toBe('Cryptography');
expect(form.controls.abbreviation.value).toBe('CRY');
expect(form.controls.description.value).toBe('Cryptographic challenges');
expect(result.abbreviationReadonly).toBe(true);
expect(result.iconPreview).toBeNull();
});
it('updates iconPreview when switching between two system rows', () => {
const form = buildForm();
const first = syncCategoryForm(
form,
'edit',
makeRow({
name: 'Cryptography',
abbreviation: 'CRY',
description: 'Cryptographic challenges',
iconPath: '/uploads/icons/CRY.png',
}),
);
expect(first.iconPreview).toBe('/uploads/icons/CRY.png');
expect(form.controls.name.value).toBe('Cryptography');
const second = syncCategoryForm(
form,
'edit',
makeRow({
name: 'Hardware',
abbreviation: 'HW',
description: 'Hardware challenges',
iconPath: '/uploads/icons/HW.png',
}),
);
expect(second.iconPreview).toBe('/uploads/icons/HW.png');
expect(form.controls.name.value).toBe('Hardware');
expect(form.controls.abbreviation.value).toBe('HW');
expect(form.controls.description.value).toBe('Hardware challenges');
});
});