Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

14 — QA Checklist (Academics Module)

Module QA specific to framework + shared 00-shared/10. Every check derives from a specific backend behavior so failure analysis is direct.


1. Functional — CRUD & uniqueness

  • Create academic year returns 200 with status:"upcoming" default (academic-year.service.ts:30-33).
  • Duplicate year name → 409 DUPLICATE_RESOURCE message verbatim ("Academic year "X" already exists.", academic-year.service.ts:27-28).
  • Duplicate grade name → 409 (grade.service.ts:23).
  • Duplicate subject code → 409 (subject.service.ts:23-24); race path: two parallel POSTs → one succeeds, second 409 via unique index (subject.schema.ts:38) — client must surface as duplicate, not 5xx.
  • Duplicate class (academicYearId, gradeId, sectionId, name)server accepts (OQ-4) — client shows "Duplicate class?" warn before submit; verify no false block when the exact combo differs by one field.
  • Duplicate section (gradeId, name) → server accepts (OQ-4) — client warning only.
  • Duplicate assignment (classId, subjectId) → server accepts (OQ-4) — client picker disables already-assigned pairs.
  • Update endpoints accept partial bodies only (all-optional DTOs) — empty PATCH {} returns 200 unchanged doc, no error.
  • PATCH /academic-years/:id with duplicate nameno 409 (update path has no name check, OQ-2) — client pre-checks from cache.
  • PATCH /subjects/:id cannot change theoryMarks/practicalMarks — fields not in DTO (update-subject.dto.ts:4-42); unknown fields → 400 VALIDATION_ERROR (whitelist validation).

2. Functional — set-current & status

  • set-current un-flags ALL other years, flags target, sets status:"active" (academic-year.service.ts:69-83) — verify with 3 years in DB.
  • set-current on an archived year → becomes active (server allows; confirm copy reflects).
  • Exactly one isCurrent:true after any sequence of set-current calls (singleton rule, COLLECTIONS.md:1023).
  • Year status accepts only upcoming|active|archived at the client (enum academic-year.schema.ts:7-11); server accepts any string (IsString) — client must never send unknown values.
  • Grade/section/class status free string: UI treats only 'active' as active; arbitrary server value renders as archived-style badge without crashing (OQ-2).

3. Functional — hierarchy & ordering

  • Grades list ordered by displayOrder asc, default 0 first (grade.service.ts:37-39); two grades with same order → stable (insertion) order, no client re-sort.
  • Years list default createdAt:-1 (newest first) (academic-year.service.ts:45-47).
  • Classes/sections/subjects keep insertion order (class.service.ts:41-53, section.service.ts:41-48, subject.service.ts:35-47).
  • GET /classes/by-year/:id returns only that year's classes; GET /sections/by-grade/:id only that grade's sections (server-scoped).
  • sort=-createdAt query param maps to desc (prefix - handling, academic-year.service.ts:45-47); sort=displayOrder on grades works.
  • Pagination: limit clamped 1–100 (400 outside), page ≥ 1; meta totalPages/hasNext/hasPrevious correct at boundaries (page 1 of 1, last page, empty list → totalPages:1 via Math.max(1,…), pagination-query.dto.ts:46).
  • q param sent → server ignores it; list returns unfiltered (OQ-7) — client search is local; verify no error thrown.

4. Functional — delete & cascade behaviour

  • Delete (any entity) = soft delete: row disappears from lists; reappears if isDeleted filter changes (platform admin view, base.repository.ts:20-30) — client never relies on permanence.
  • Delete grade with live sections/classes → no server error, no cascade (OQ-3); orphan sections/classes still appear under other views; client orphan banners fire.
  • Delete class → timetable/attendance rows referencing it still exist server-side (OQ-3); client re-resolves and shows "class no longer exists".
  • Delete assignment → 404 on second delete ("Assignment not found.", subject-assignment.service.ts:34) → UI treats as removed.
  • version increments on every update (base.repository.ts:63) — verify via doc read (no client optimistic concurrency).

5. Functional — referential & tenant isolation

  • Class create with non-existent academicYearId/gradeId/sectionIdserver accepts (no ref checks, class.service.ts:16-18) — client pickers prevent, but a pasted bad ObjectId is still renderable as "Unknown".
  • tenantId never sent in body/query; cross-tenant ID returns 404 (scoped filter, base.repository.ts:38-40) — verify with two tenants.
  • Platform admin sees all tenants' data only via admin bypass (base.repository.ts:21-23) — client never renders this path.
  • All 6 controllers require valid JWT (401 without); no RBAC — any authed role can create/update/delete (OQ-1) — QA must confirm with a student token: writes currently succeed (document as known gap).
  • class.campusId accepts any ObjectId (ref typo Class, class.schema.ts:12-13, OQ-6) — client hides field; roomId free string round-trips.

6. Visual / layout

  • Dark-light parity across all lists, explorer, matrix sheet (no hardcoded colors).
  • Current-year row distinct (primaryContainer) in both themes; status badges legible.
  • Chips wrap at 200% text scale; SectionChips "+N more" overflow works.
  • Long subject names + codes truncate with ellipsis, never overflow the roster table.
  • Empty states per entity with correct primary CTA.

7. Accessibility (00-shared/09)

  • TalkBack: explorer tree fully operable (expand/collapse announced, Semantics(expanded:)); cascading pickers announce enabled/disabled state.
  • VoiceOver separately.
  • Keyboard-only desktop: Tab order Year→Grade→Section→Class→Subject; N, Ctrl+Enter, Esc work; focus ring visible.
  • Conflict banners in live region; snackbars announced.
  • Contrast: body 4.5:1; conflict tertiaryContainer 3:1 for borders/dots.
  • Reduced motion: no slide/scale; tree animates as fades only.

8. Performance

  • 12 grades × 4 sections × 40 classes: list first frame ≤ 200 ms skeleton → content ≤ 2 s (00-shared/10 §1).
  • Explorer with 200 nodes: expand/collapse no jank (≤ 50 open nodes policy, 07 §1); children lazy-loaded.
  • Reference cache load-once: navigating between 6 list screens triggers ≤ 5 collection fetches total (then cache hits).
  • Infinite scroll no duplicate requests; hasNext:false stops loader.
  • Memory stable over 20-min session with roster cache LRU 20 (13 §1).

9. Offline & network (00-shared/10 §2)

  • Offline: all writes blocked + banner; reads render from 24 h cache (with stale badge "Offline — data from HH:mm").
  • 24 h TTL expiry → revalidate on next foreground; failure → keep stale + banner.
  • Slow network × 429 countdown correct; no auto-retry on 429.
  • 5xx on create → generic + requestId, form preserved, retry safe (idempotency not guaranteed — 409 tells the truth on retry).

10. Tablet / desktop

  • Master-detail at ≥ 840 dp: list left, detail right; back button pops branch not shell.
  • RosterTable sticky header, column sorting is disabled (server order preserved).
  • Right-click = context menu; hover tooltips on conflict icons (500 ms).
  • Scrollbar always visible.

11. Localization

  • All strings i18n; server 409 messages shown as-is (not translated) for debug accuracy — policy decision: translate or quote server copy verbatim (recommend verbatim + prefix icon).
  • Dates locale-formatted (dd MMM yyyy) but submitted as ISO YYYY-MM-DD (IsDateString, create-academic-year.dto.ts:9-15).
  • RTL: chips, tree indents, roster columns mirror.

12. Dark mode / animations / security

  • Full visual pass; conflict/error surfaces distinct in dark (11 §6).
  • Motion reduced-motion pass; no flash on set-current badge swap.
  • No IDs/tenant data in analytics events; screenshots blur class/student names (proposed).
  • Error screens never leak stack traces; only error.code + requestId shown.

13. Server-backed edge mirrors

  • 409 paths: year name, grade name, subject code (create) — exact server copy asserted in widget tests.
  • 404 paths: every GET/:id, DELETE/:id, set-current with stale id → treated as removed/refreshed, never a stuck screen.
  • Year with endDate < startDate → server accepts (no check, OQ-5) — client blocks before submit.
  • Subject marks cross-field violations → server accepts (OQ-5) — client blocks before submit; verify server accepts direct API call (documented gap).

QA scripts

  • Golden per component (HierarchyTree, SectionChips, SubjectAssignmentRow, YearStatusBadge, CascadingEntityPicker) light/dark × phone/tablet/desktop.
  • E2E (device-cloud P0): fresh tenant → create year → set current → create grades 1–12 → sections A–D → classes → create subjects → assign 3 teachers → verify timetable/attendance screens reflect the new class.
  • E2E conflict drill: create duplicate class + duplicate assignment via raw API, verify client banners + no crash.
  • E2E multi-device: set-current from device B while device A is on years list → A corrects badge on focus.
  • Security smoke: student-token write attempt (documents OQ-1 gap); cross-tenant ID read → 404.