feat: Admin Area Blog and Blog Page 1.00

This commit is contained in:
OpenVelo Agent
2026-07-23 10:37:50 +00:00
parent 79a9adf720
commit f501a9046a
4 changed files with 138 additions and 82 deletions
@@ -0,0 +1,67 @@
import '@angular/compiler';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const sourcePath = resolve(
__dirname,
'../../frontend/src/app/features/admin/blog/blog-form-modal.component.ts',
);
const source = readFileSync(sourcePath, 'utf8');
describe('BlogFormModalComponent — Escape-to-close contract', () => {
it('imports HostListener from @angular/core', () => {
const importBlock = source.match(
/import\s*\{[\s\S]*?\}\s*from\s*'@angular\/core';/,
);
expect(importBlock).not.toBeNull();
expect(importBlock![0]).toMatch(/\bHostListener\b/);
});
it('registers a @HostListener for document:keydown.escape', () => {
expect(source).toMatch(
/@HostListener\(\s*['"]document:keydown\.escape['"]\s*\)/,
);
});
it('guards the Escape handler with open() so a closed modal is a no-op', () => {
const handler = extractEscapeHandler(source);
expect(handler).not.toBeNull();
expect(handler).toMatch(/this\.open\(\)/);
});
it('guards the Escape handler against in-flight saves (no cancel while saving)', () => {
const handler = extractEscapeHandler(source);
expect(handler).not.toBeNull();
expect(handler).toMatch(/!this\.saving\(\)/);
});
it('routes Escape to the existing onCancel() so the parent closes the modal without saving', () => {
const handler = extractEscapeHandler(source);
expect(handler).not.toBeNull();
expect(handler).toMatch(/this\.onCancel\(\)/);
});
it('keeps the existing onCancel() emit intact as a regression guard', () => {
const onCancel = extractMethod(source, 'onCancel');
expect(onCancel).not.toBeNull();
expect(onCancel).toMatch(/this\.cancel\.emit\(\s*\)/);
});
});
function extractEscapeHandler(src: string): string | null {
// Capture the onEscape handler body — must contain the guard + onCancel call.
const m = src.match(
/@HostListener\(\s*['"]document:keydown\.escape['"]\s*\)\s*onEscape\s*\(\s*\)\s*:\s*void\s*\{([^}]*)\}/,
);
return m ? m[1] : null;
}
function extractMethod(src: string, name: string): string | null {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Match the method header (with optional TypeScript return-type annotation) and capture the body.
const re = new RegExp(
`(?:public\\s+|private\\s+|protected\\s+)?${escaped}\\s*\\([^)]*\\)\\s*(?::\\s*[A-Za-z_<>|[\\]\\s,]+)?\\s*\\{([\\s\\S]*?)\\n\\s*\\}`,
);
const m = src.match(re);
return m ? m[1] : null;
}