6.9 KiB
6.9 KiB
Implementation Plan: Job 914 — Escape must close the New post overlay without saving
1. Architectural Reconnaissance
- Codebase style & conventions:
- Angular 17+ standalone components,
ChangeDetectionStrategy.OnPush, signal inputs (input()), signal outputs (output()), the new@ifcontrol flow, andeffect()for reactive side effects. - Backend is NestJS with TypeORM; out of scope for this Job (no DB/API changes).
- The frontend admin blog feature lives under
frontend/src/app/features/admin/blog/. The pure helpers live inblog-form.pure.ts; the modal isblog-form-modal.component.ts; the smart page isblog.component.ts. - Escape-to-close is the established convention for every modal in this codebase. Every other modal uses
@HostListener('document:keydown.escape')on the component class and emits itscanceloutput:frontend/src/app/features/setup/setup-create-admin.component.ts:103frontend/src/app/features/admin/player-delete-modal.component.ts:61frontend/src/app/features/shell/change-password/change-password-modal.component.ts:156frontend/src/app/features/challenges/challenge-modal.component.ts:174frontend/src/app/features/admin/challenges/challenge-import-modal.component.ts:105frontend/src/app/features/admin/challenges/challenge-form-modal.component.ts:404frontend/src/app/features/admin/challenges/challenge-delete-modal.component.ts:56frontend/src/app/features/landing/landing.component.ts:102
- The very similar
change-password-modal.component.ts:156–159guards the handler withif (this.open() && !this.submitting()) this.onCancel();. We will mirror that exact contract for the blog form modal (swappingsubmitting()forsaving()). - The blog form modal is the only modal missing this listener. This is the bug described in the Job.
- Angular 17+ standalone components,
- Data Layer: No database changes — this is purely a frontend modal interaction fix.
- Test Framework & Structure:
- Jest 29 with
ts-jest, frontend project runs injsdom(tests/jest.config.js). - Single test entry:
npm testruns both backend and frontend projects;npm run test:frontendruns only frontend. - Tests live under
tests/frontend/(front-end) andtests/backend/(back-end). Source code lives underfrontend/src/app/andbackend/src/. Tests must NEVER live inside source folders. - Existing frontend tests are predominantly pure-helper tests (no TestBed). The
challenge-card-accessibility.spec.tspattern uses areadFileSyncsource-text assertion against the component template, which is the safest pattern for testing that a@HostListeneris wired into the component without standing up the full Angular runtime.
- Jest 29 with
- Required Tools & Dependencies: None. No new packages, globals, or
setup.shchanges are needed —@angular/corealready exportsHostListener, and Jest/jsdom is already configured.
2. Impacted Files
- To Modify:
frontend/src/app/features/admin/blog/blog-form-modal.component.ts— add the@HostListener('document:keydown.escape')handler that calls the existingonCancel()when the modal is open and not currently saving.
- To Create:
tests/frontend/blog-form-modal-escape.spec.ts— minimal, focused regression test that verifies the Escape listener is wired and routed correctly. Mirrors the existing source-text snapshot pattern fromtests/frontend/challenge-card-accessibility.spec.tsso it stays free of TestBed/Angular runtime setup and runs instantly undernpm test.
3. Proposed Changes
-
Add an Escape handler to
BlogFormModalComponentinfrontend/src/app/features/admin/blog/blog-form-modal.component.ts:- Import
HostListenerfrom@angular/corealongside the existingChangeDetectionStrategy,ChangeDetectorRef,Component,DestroyRef,effect,inject,input,output,signalimports. - Add the following method on the class (mirroring
change-password-modal.component.ts:156–159):@HostListener('document:keydown.escape') onEscape(): void { if (this.open() && !this.saving()) this.onCancel(); } - This reuses the existing
onCancel()(blog-form-modal.component.ts:210) which already emitscancel. The parentAdminBlogComponent.closeModal()(blog.component.ts:188) clearsmodalMode,editing,formError,saving, etc., so no post is created or modified. - Rationale for the
open() && !saving()guard: the modal is conditionally rendered via@if (open())in the template, so the listener will fire when the modal is mounted regardless ofopen()'s value; we explicitly checkopen()for safety and parity withchange-password-modal.component.ts, and we skip the handler whilesaving()is true so an in-flight create/publish cannot be cancelled mid-request (the existingPublish/Save draftbuttons remain[disabled]while saving and the parent also blocks the toolbar viaactionInFlight). - No template, styles, or HTML attribute changes are needed. The
Cancelbutton, backdrop click, and the new Escape keypress all flow through the sameonCancel()→canceloutput.
- Import
-
No backend / API changes. The bug is purely a missing keyboard handler on the client. The REST endpoints (
POST /api/v1/admin/blog/posts,PATCH /api/v1/admin/blog/posts/:id) are already correct; the failing test scenario was a frontend interaction that never called the API. -
No
setup.sh/ dependency changes.HostListeneris part of@angular/core, which is already a dependency.
4. Test Strategy
- Target Unit Test File:
tests/frontend/blog-form-modal-escape.spec.ts(new file, in the dedicatedtests/frontend/folder, runnable vianpm testfrom the repo root). - Mocking Strategy: No runtime mocking required. The test uses the same source-text assertion pattern as
tests/frontend/challenge-card-accessibility.spec.ts: readfrontend/src/app/features/admin/blog/blog-form-modal.component.tsonce withfs.readFileSyncand assert that:HostListeneris imported from@angular/core(presence of the symbol in the import block).- The exact decorator
@HostListener('document:keydown.escape')is present on the class. - The decorated handler method calls
this.onCancel(). - The handler is guarded by
this.open()andthis.saving()(to prevent Escape from cancelling an in-flight save, matching thechange-password-modalcontract). - The existing
onCancel()method still emitscancel(regression guard that the wiring remains intact).
- The test must import
'@angular/compiler'at the top for parity with the rest of the frontend suite (seetests/frontend/blog-admin-form.spec.ts:1). - Avoid standing up TestBed, ComponentFixture, or any DOM/jest-environment-jsdom behavior — keep the test pure and instant, per the
IMPORTANT RULE CONCERNING TESTSinstructions. - All tests must pass via
npm test(ornpm run test:frontend) inside the container without any UI/browser interaction.