115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
import '@angular/compiler';
|
|
import { FormBuilder } from '@angular/forms';
|
|
import {
|
|
PostFormGroup,
|
|
buildPostBody,
|
|
statusBadgeClass,
|
|
statusLabel,
|
|
syncPostForm,
|
|
validatePostForm,
|
|
} from '../../frontend/src/app/features/admin/blog/blog-form.pure';
|
|
import { AdminBlogPost } from '../../frontend/src/app/core/services/blog.service';
|
|
|
|
function buildForm(): PostFormGroup {
|
|
const fb = new FormBuilder();
|
|
return fb.nonNullable.group({
|
|
title: fb.nonNullable.control(''),
|
|
bodyMd: fb.nonNullable.control(''),
|
|
});
|
|
}
|
|
|
|
function makePost(overrides: Partial<AdminBlogPost> = {}): AdminBlogPost {
|
|
return {
|
|
id: 'p-1',
|
|
title: 'Existing Title',
|
|
bodyMd: 'existing body',
|
|
status: 'draft',
|
|
createdAt: '2026-07-21T10:00:00.000Z',
|
|
updatedAt: '2026-07-22T10:00:00.000Z',
|
|
publishedAt: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('validatePostForm', () => {
|
|
it('rejects empty title', () => {
|
|
const r = validatePostForm({ title: '', bodyMd: 'x' });
|
|
expect(r.ok).toBe(false);
|
|
expect(r.errors.title).toBeDefined();
|
|
});
|
|
|
|
it('rejects whitespace-only title', () => {
|
|
const r = validatePostForm({ title: ' \t ', bodyMd: 'x' });
|
|
expect(r.ok).toBe(false);
|
|
expect(r.errors.title).toBeDefined();
|
|
});
|
|
|
|
it('rejects title longer than 200 characters', () => {
|
|
const r = validatePostForm({ title: 'a'.repeat(201), bodyMd: '' });
|
|
expect(r.ok).toBe(false);
|
|
expect(r.errors.title).toBeDefined();
|
|
});
|
|
|
|
it('rejects body longer than 200000 characters', () => {
|
|
const r = validatePostForm({ title: 'ok', bodyMd: 'x'.repeat(200_001) });
|
|
expect(r.ok).toBe(false);
|
|
expect(r.errors.bodyMd).toBeDefined();
|
|
});
|
|
|
|
it('accepts a valid title and empty body', () => {
|
|
const r = validatePostForm({ title: ' Real Title ', bodyMd: '' });
|
|
expect(r.ok).toBe(true);
|
|
expect(r.errors.title).toBeUndefined();
|
|
expect(r.errors.bodyMd).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('syncPostForm', () => {
|
|
it('prefills the form for edit mode with a post', () => {
|
|
const form = buildForm();
|
|
syncPostForm(form, 'edit', makePost());
|
|
expect(form.controls.title.value).toBe('Existing Title');
|
|
expect(form.controls.bodyMd.value).toBe('existing body');
|
|
expect(form.controls.title.pristine).toBe(true);
|
|
expect(form.controls.title.touched).toBe(false);
|
|
});
|
|
|
|
it('clears the form for create mode with null post', () => {
|
|
const form = buildForm();
|
|
syncPostForm(form, 'edit', makePost());
|
|
expect(form.controls.title.value).toBe('Existing Title');
|
|
syncPostForm(form, 'create', null);
|
|
expect(form.controls.title.value).toBe('');
|
|
expect(form.controls.bodyMd.value).toBe('');
|
|
});
|
|
|
|
it('re-populates when invoked a second time with a different post', () => {
|
|
const form = buildForm();
|
|
syncPostForm(form, 'edit', makePost({ title: 'Alpha' }));
|
|
expect(form.controls.title.value).toBe('Alpha');
|
|
syncPostForm(form, 'edit', makePost({ title: 'Bravo', bodyMd: 'b body' }));
|
|
expect(form.controls.title.value).toBe('Bravo');
|
|
expect(form.controls.bodyMd.value).toBe('b body');
|
|
});
|
|
});
|
|
|
|
describe('buildPostBody', () => {
|
|
it('trims the title and passes body through', () => {
|
|
expect(buildPostBody({ title: ' hello ', bodyMd: 'md' })).toEqual({
|
|
title: 'hello',
|
|
bodyMd: 'md',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('statusBadgeClass / statusLabel', () => {
|
|
it('maps draft → badge-draft and "Draft"', () => {
|
|
expect(statusBadgeClass('draft')).toBe('badge-draft');
|
|
expect(statusLabel('draft')).toBe('Draft');
|
|
});
|
|
it('maps published → badge-published and "Published"', () => {
|
|
expect(statusBadgeClass('published')).toBe('badge-published');
|
|
expect(statusLabel('published')).toBe('Published');
|
|
});
|
|
});
|