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 { 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'); }); });