221 lines
7.5 KiB
TypeScript
221 lines
7.5 KiB
TypeScript
import '@angular/compiler';
|
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
|
import {
|
|
syncCategoryForm,
|
|
CategoryFormMode,
|
|
CategoryFormSubmit,
|
|
} 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');
|
|
});
|
|
|
|
it('keeps user (non-system) abbreviation editable and emits the uppercased abbreviation', () => {
|
|
const form = buildForm();
|
|
const result = syncCategoryForm(
|
|
form,
|
|
'edit',
|
|
makeRow({
|
|
id: 'user-cat',
|
|
name: 'Cat A Test T007',
|
|
abbreviation: 'CATA',
|
|
description: 'd',
|
|
isSystem: false,
|
|
systemKey: null,
|
|
}),
|
|
);
|
|
expect(result.abbreviationReadonly).toBe(false);
|
|
|
|
form.controls.name.setValue('Cat Z Renamed T007');
|
|
form.controls.abbreviation.setValue('zzza');
|
|
form.controls.description.setValue('Renamed to ZZZA for T007');
|
|
|
|
const v = form.getRawValue();
|
|
const emitted: CategoryFormSubmit = {
|
|
mode: 'edit',
|
|
name: v.name,
|
|
abbreviation: v.abbreviation.toUpperCase(),
|
|
description: v.description,
|
|
};
|
|
expect(emitted.abbreviation).toBe('ZZZA');
|
|
expect(emitted.name).toBe('Cat Z Renamed T007');
|
|
expect(emitted.description).toBe('Renamed to ZZZA for T007');
|
|
});
|
|
}); |